Install on React (Vite or Create React App)
Add one script tag to your app's HTML page, publish, and open the site. This page covers a React single-page app built with Vite or Create React App, and apps that would rather load the tag from code. For Next.js, see Install on Next.js.
Platform steps checked against the official documentation on 2026-11-03: Vite: index.html and project root, React: Sunsetting Create React App, React: StrictMode.
Quick install
Settings › Channels in the Telltale dashboard shows this tag with your real key already filled in. Copy it from there.
<script src="https://app.trytelltale.dev/sdk/v1.js" data-telltale-key="YOUR_SITE_PUBLISHABLE_KEY"></script>Vite
- Open
index.htmlat the root of your project (Vite keeps it there, not inpublic/). - Paste the tag inside
<head>, before the<script type="module" src="/src/main.tsx">line. - Build and deploy as you normally do.
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script src="https://app.trytelltale.dev/sdk/v1.js" data-telltale-key="YOUR_SITE_PUBLISHABLE_KEY"></script>
<title>My app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>Create React App
- Open
public/index.html. - Paste the tag inside
<head>. - Run
npm run buildand deploy.
The React team deprecated Create React App for new apps on February 14, 2025; it still works in maintenance mode. If you move to Vite later, move the tag to Vite's index.html.
Load it from code instead
If you cannot edit the HTML page (for example a micro-frontend), add the tag once from your root component. The check on the existing tag keeps React Strict Mode, which runs effects twice in development, from adding it twice.
// src/useTelltale.ts
import { useEffect } from 'react';
const SDK_URL = 'https://app.trytelltale.dev/sdk/v1.js';
export function useTelltale() {
useEffect(() => {
if (document.querySelector(`script[src="${SDK_URL}"]`)) return; // already added
const script = document.createElement('script');
script.src = SDK_URL;
script.dataset.telltaleKey = 'YOUR_SITE_PUBLISHABLE_KEY';
document.head.append(script);
// No cleanup: the tag stays for the life of the page. Removing it would not stop the SDK.
}, []);
}Call useTelltale() once in your top-level App component. The publishable key is meant for the browser; never put the server key (ao_sk_...) in your React code or in a VITE_ or REACT_APP_ variable.
Consent
The tag records from page load. That is right for sites without a cookie or consent banner.
If your site shows a banner, hold the tag: add data-telltale-wait-for-consent. A held tag sends nothing at all until you call optIn().
<script src="https://app.trytelltale.dev/sdk/v1.js" data-telltale-key="YOUR_SITE_PUBLISHABLE_KEY" data-telltale-wait-for-consent></script>In the code version, add script.dataset.telltaleWaitForConsent = '';.
Then pass the banner's decision on, wherever your banner reports it:
// When the banner allows measurement:
await window.Telltale.getClient().optIn();
// When the visitor refuses or withdraws:
await window.Telltale.getClient().optOut();Apply the saved decision on every full page load, and again each time it changes. Signing in is not consent. For a consent manager, use the Consent bridge guide and the OneTrust, Cookiebot or Sourcepoint recipe; Install on Next.js shows the bridge in a React app.
Identify the signed-in user
Telltale is most useful when each session is linked to the user and the account it belongs to. Send opaque, stable internal IDs, such as your database IDs. Never send e-mail addresses, names or phone numbers.
From your server (recommended). After sign-in, your backend links the session to the user it authenticated. This is the identity Telltale trusts. See Quickstart step 2 and Server API › Associate identity.
In the browser (a label). Once your auth code knows who is signed in, call identify(). Call reset() on logout or when the user switches account.
// src/TelltaleIdentity.tsx
import { useEffect, useRef } from 'react';
import { useAuth } from './auth'; // your own auth hook
declare global {
interface Window {
Telltale?: { getClient(): {
identify(identity: { userId?: string | null; accountId?: string | null }): Promise<void>;
reset(): Promise<void>;
optIn(): Promise<void>;
optOut(): Promise<void>;
} };
}
}
export function TelltaleIdentity() {
const { user } = useAuth(); // null when signed out
const linked = useRef<string | null>(null); // who this page last identified
useEffect(() => {
let client;
try {
client = window.Telltale?.getClient();
} catch {
return; // no Telltale client on this page
}
if (!client) return; // the tag was blocked or has not loaded
const current = user ? `${user.id}:${user.accountId}` : null;
if (current === linked.current) return;
const wasSignedIn = linked.current !== null;
linked.current = current;
// Identify on sign-in and account switch; reset only on logout (not for visitors who never signed in).
const done = user
? client.identify({ userId: user.id, accountId: user.accountId })
: wasSignedIn ? client.reset() : null;
// identify() rejects while the tag is held for consent, or when the Telltale server is unreachable.
done?.catch(() => {});
}, [user?.id, user?.accountId]);
return null;
}Render <TelltaleIdentity /> once, inside your auth provider. With the tag in index.html, the SDK has loaded before your bundle runs. If you load it from code, getClient() can still be missing on the first render; the effect runs again when the user changes, and server identify does not depend on it. A held tag forgets identity until optIn(), so identify again after the visitor allows measurement.
Content Security Policy
If your app sends a Content Security Policy, allow the Telltale server for scripts and for connections:
Content-Security-Policy: script-src 'self' https://app.trytelltale.dev; connect-src 'self' https://app.trytelltale.devOr use a per-response nonce with 'strict-dynamic', and keep connect-src https://app.trytelltale.dev. Never add 'unsafe-inline' or 'unsafe-eval'. Vite dev servers usually send no policy, so test the policy on a production build. The Content Security Policy guide has both options in full.
Check it worked
- In the Telltale dashboard, open Settings › Channels and leave the Is it working? check open.
- Choose Open my site to test it, or open your deployed app in a new tab yourself. Browse a page or two.
- Watch the check move through Script loaded on your address, First session received and, if you set up identify, Signed-in user linked. When the first session arrives, it links to it.
You can also look in your browser's DevTools › Network: sdk/v1.js should answer 200, and so should api/sdk/init. If it stays at the first step, see It's not working.
Common mistakes
- Testing on
localhostagainst the live Site. Your Site lists your live address. Addhttp://localhost:5173(Vite) orhttp://localhost:3000(Create React App) to a separate test Site, so test traffic never mixes with real numbers. wwwand the bare domain.https://www.example.comandhttps://example.comare different addresses. Add every one your app is served from under Settings › Channels › Website addresses, and staging domains too.- The tag in
src/or in a component's JSX. A<script>written in JSX does not run. Useindex.html, or the hook above. - Two installs. Do not combine this tag with Google Tag Manager or Cloudflare for the same Site on the same page.
- Ad blockers. Some privacy extensions block third-party scripts. Test in a window without extensions.