Install
- Add your site. Start a free trial, then enter the site’s name and domain.
- Paste the snippet before
</head>on every page. The dashboard shows it with your project and key filled in. - Visit your site. The first visit shows up in the dashboard within seconds.
<script src="https://onprod.app/sdk.js"></script>
<script>
if (window.OnProd) OnProd.init({
projectId: "[your project]",
endpoint: "https://onprod.app",
apiKey: "[your ingest key]",
});
</script>- The key in the snippet can only send data, so it is safe in a public page.
- The
if (window.OnProd)check keeps your page free of errors when a blocker stops the script. - The script loads from OnProd, so you get updates without a deploy.
Check it works
Open your site in another tab. Until data arrives the Overview says “Waiting for the first visit”, then it fills in by itself. Errors and failed requests show up as issues while you are still on the page; on-screen messages follow 30 minutes after the visit ends.
Still nothing after a visit? Privacy blockers such as EasyPrivacy can stop the script or its requests: turn yours off and try again. Visitors with a blocker may not be recorded at all.
Options
Everything has a sensible default. These are the ones worth knowing:
if (window.OnProd) OnProd.init({
projectId: "[your project]",
endpoint: "https://onprod.app",
apiKey: "[your ingest key]",
release: "[your build id]",
redactUrl: (url) => url.replace(/\/customers\/[^/?#]+/, "/customers/:id"),
replay: { maskAllText: true },
});| Option | What it does |
|---|---|
release | Your build, such as the git SHA. Issues show the release they started in (see Releases). |
consentMode | How much is captured before consent (see Consent). |
redactUrl | Rewrites every URL before it is sent, for ids and e-mail addresses in paths. Tokens, codes and passwords in query strings are redacted already. |
redactErrorMessage | Rewrites error text, which can quote the page. |
replay.maskAllText | Masks all text in replays; the layout stays visible (see Masking). |
automation | "record_without_replay" skips replay for automated browsers, such as your end-to-end tests. |
sessionTimeout | A new session starts after 30 minutes without activity. |
Custom events
Page views, clicks, form submits, scroll depth, errors and failed requests are captured with no code. Track anything else:
OnProd.track("plan_selected", { plan: "growth" });Custom events can be charted in Trends and used as funnel steps, and they are sent even for visitors who are only counted.
Signed-in users
// After sign-in, and on start when someone is already signed in
OnProd.identify(user.id);
// On sign-out
OnProd.reset();Without it, visitors and retention are counted per browser. reset() also keeps the next person on a shared computer apart.
Your own team
OnProd.identify(user.id, { $internal: user.isStaff });Their sessions are marked internal and Filtered out of reports and AI Insights. For staff who never sign in, run OnProd.markInternal(true) once in the browser console. Visits to hosts you haven’t allowed, such as staging or a preview, are filtered out too (Settings › General).
Releases
Set release to a value that changes with each deploy:
// VITE_RELEASE=$(git rev-parse --short HEAD) vite build
release: import.meta.env.VITE_RELEASE,Issues record the release they first appeared in, a resolved issue that comes back shows the release it came back in, and AI Insights look harder at the 72 hours after a new release.
Single-page apps
Nothing to add. Route changes through pushState, replaceState and the back button count as page views, and replays mark each route.
Masking
Masking happens in the browser, before anything is sent. What visitors type is always masked; you choose what else.
<p data-at-mask>Jane Smith, 0412 345 678</p> <!-- recorded as asterisks -->
<div data-at-block>…</div> <!-- left out: an empty box -->Or mask all text with replay: { maskAllText: true }. Masking is the site owner’s job: OnProd can’t find personal data in page text, and nothing can be masked after it was recorded. With AI Insights on, text you didn’t mask can be part of the evidence the AI reads.
Consent
Choose how strict to be in Settings › General › Visitor privacy:
| Setting | What happens |
|---|---|
| Your site’s consent setup decides | The default. Visits are recorded in full; your own consent banner decides. |
| Count without consent in opt-in regions | Visitors in the EU, UK, Switzerland and Quebec are only counted until they consent, and so are visitors in US privacy-law states whose browser sends Global Privacy Control. |
| Count without consent everywhere | Everyone is only counted until they consent. |
Counted means page views, scroll depth and time on the page, with no replay, no IDs and nothing stored in the browser. Your banner upgrades the visit:
OnProd.init({ /* … */ consentMode: "count" }); // only count until consent
OnProd.grantConsent(); // from your banner: record everything, same visit
OnProd.revokeConsent(); // stop, and delete what is stored in the browserDeleting someone’s data
Create a key with the Erase scope in Settings › API keys, keep it on your server, and send the id you passed to identify():
curl -X POST https://onprod.app/v1/user-deletions \
-H "Authorization: Bearer $ONPROD_ERASURE_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id": "user_123"}'Their sessions, events, replays and the Insights that cite them are erased, most of it within a minute; a second sweep five minutes later catches anything still on its way. For a visitor who never signed in, send {"anonymous_id": "a_…"} with the value getAnonymousId() returned in their browser. You can also do it by hand in Settings › User data.
Stuck? E-mail [email protected].