Documentation
Browser installation & consent
Install with one script while keeping collection and integrations off by default
The browser bundle provides the same identity, session, attribution, retry, and autocapture behavior as the npm SDK without requiring a build step. It starts with analytics consent denied: no identifier is created, nothing is written to storage, and no event is sent until your site explicitly updates consent.
Copy-paste installation
Run bun run pack:local in the Realanalytics repository. Copy dist/local-packages/browser.global.js into your site'spublic/vendor/realanalytics/ directory. Set the public key and ingest endpoint for the same environment. The queue stub runs before the bundle.
<script>
window.realanalytics = window.realanalytics || function () {
(window.realanalytics.q = window.realanalytics.q || []).push(arguments)
}
</script>
<script
async
src="/vendor/realanalytics/browser.global.js"
data-public-key="pk_live_your_public_key"
data-endpoint="https://your-ingest-host.example"
></script>Serve the locally built bundle from your own site. The placeholder endpoint must be replaced with your deployed ingest service. The dashboard install page uses its configured REALANALYTICS_INGEST_URL; collection stays denied until your CMP grants consent.
A pk_live_ project key is public and is the only key that belongs in browser HTML. Never place an sk_live_ project secret in a script, browser environment variable, tag manager, or client bundle.
Connect your consent manager
Send the decision recorded by your CMP or consent banner. Analytics, advertising user data, and advertising personalization are independent purposes; do not infer the advertising decisions from analytics consent.
// Call after the visitor saves a decision in your CMP.
realanalytics('consent', {
analytics: true,
adUserData: false,
adPersonalization: false,
recordedAt: new Date().toISOString(),
source: 'cmp:your-provider'
})The values may be booleans or granted, denied, andunknown. The SDK stores the complete snapshot and attaches it to every event so later attribution uses the decision that existed when the event occurred.
Withdrawal
// Purges this project's local identity, session,
// attribution touchpoints, pending events, and offline queue.
realanalytics('optOut')Withdrawal also expires the SDK's first-party _fbp and_fbc cookies so a later grant cannot restore the withdrawn browser identity. Global Privacy Control and Do Not Track are respected by default and prevent an API call from overriding the browser signal.optIn() grants analytics only; both advertising purposes remainunknown. Use the complete consent object when your CMP exposes separate choices.
Other tabs and instances
A consent decision applies to every SDK instance with the same public key in the browser. That includes other instances on the page and in other tabs. The SDK tells them through BroadcastChannel, and uses the storage event as a cross-tab fallback.
- An analytics withdrawal purges every instance's queued and in-flight events, retries, offline queue, identifiers, session, attribution, Meta cookies, and autocapture.
- An ad-user-data withdrawal removes click and browser identifiers from every instance's pending events and closes each Meta Pixel gate.
- Grants propagate too. Every instance follows the most recently saved decision, so a later grant in any tab is honored.
- Global Privacy Control and Do Not Track always win in an instance that respects them. That instance ignores decisions from other tabs.
An instance checks the saved decision before it sends or stores identifiers, attribution, sessions, or offline events. A tab that missed a message therefore cannot restore withdrawn data. That includes a withdrawal followed by a new grant.
Withdrawal record
When the visitor withdraws a purpose they had granted (analytics, ad user data, or ad personalization), the instance where they decided sends one record to your ingest endpoint. It is sent before identifiers are purged, so server-side attribution can apply the opt-out. Other instances that receive the change do not send one.
{
"apiKey": "pk_live_your_public_key",
"type": "consent_update",
"consentUpdate": {
"updateId": "5f0c7a52-3c1e-4d8a-9b6f-2a41d3e8c901",
"distinctId": "<the SDK's random visitor ID>",
"consent": {
"analytics": "denied",
"adUserData": "denied",
"adPersonalization": "denied",
"recordedAt": "2026-09-30T10:15:00.000Z",
"source": "api:opt-out"
}
}
}The record names only the SDK's random visitor ID, never your person ID: a public key does not prove who sent it, and a person ID could be guessed. The server applies it to that visitor's consented identity links, which is how it reaches the identified person. The SDK sends it with a keepalive fetch and keeps it in a small outbox until the Worker acknowledges or permanently rejects it, retrying later in the page and on the next page load for up to 29 days. The outbox holds only the public key, a random update ID, the visitor ID and the new consent state. Nothing is sent under Global Privacy Control or Do Not Track, or before the SDK has created an identifier. Keep recording consent on your server through the server consent API. That remains the authoritative path.
Track and identify
realanalytics('track', 'form_submitted', {
form: 'hero'
})
// Use an internal opaque ID, not an email address or phone number.
realanalytics('identify', 'person_123', {
plan: 'pro'
})Calls made before analytics consent is granted are discarded rather than replayed later. After consent, the SDK preserves a project-scoped anonymous ID and stores the identified person ID separately so server conversions can join to the original landing-page touchpoint.
Verify the installation locally
const status = realanalytics.verify()
console.log(status.ready) // true
console.log(status.verification) // "local"
console.log(status.consent)
console.log(status.pendingEvents)verify() and status() only inspect local SDK state; they do not make a verification request. The bundle also emitsrealanalytics:ready and realanalytics:error custom events for an installation wizard or browser test. The ready event detail is sanitized and omits identity values; a direct local status call can include them for debugging.
window.addEventListener('realanalytics:ready', (event) => {
console.log(event.detail)
})
window.addEventListener('realanalytics:error', (event) => {
console.error(event.detail.message)
})Confirm delivery, then debugger observation
window.addEventListener('realanalytics:success', (event) => {
console.log('newly accepted', event.detail.acceptedEvents)
console.log('idempotent duplicates', event.detail.duplicateEvents)
console.log('still pending', event.detail.pendingEvents)
})
// Call after consent and after a real test-page action queues an event.
await realanalytics.flush()flush() waits for the current delivery attempts and retries to settle. Its completion alone is not a receipt. Arealanalytics:success detail reports exact receipt counts:acceptedEvents is newly accepted events,duplicateEvents is the sum of in-batch and previously accepted duplicates, and pendingEvents is work still held locally. Delivery failures or rejections use the saferealanalytics:error detail. An ambiguous or failed receipt can remain durably pending after flush() returns. Neither signal requires a secret or exposes event payloads and identifiers.
A receipt is separate from query visibility. Open the Event Debugger and search for the event name or event ID to confirm it is observable through the analytics query path. A duplicate receipt may refer to an event that was already visible.
Script configuration
| Attribute | Behavior |
|---|---|
data-public-key | Required project public key. data-key is also accepted. |
data-endpoint | Optional HTTPS ingest endpoint. HTTP is accepted only for local or .test hosts. |
data-autocapture | true or false; autocapture starts only after consent. |
data-config | Strict JSON containing only the three options above. |
The same fields can be assigned to window.realanalyticsConfigbefore loading the bundle; script attributes win. Consent and destination settings are rejected in loader configuration.
Integrations are optional
Meta, Google, and Attio are off by default. Installing the browser bundle does not connect or enable any destination. Attio is only an optional CRM conversion source; it is not required for browser or server tracking.