Skip to content

Web SDK reference

@narrato-app/sdk-web is the browser SDK: track events, identify users, auto-capture page views. Zero runtime dependencies, ~3.8 KB gzipped, shipped as ESM, CJS, and a script-tag (IIFE) build with TypeScript declarations.

  • Offline-safe: events persist in localStorage and survive page loads.
  • Batched delivery with retry + exponential backoff; sendBeacon on page hide so unload doesn’t lose events.
  • Sessions with 30-minute inactivity renewal.
  • SSR-safe: outside a DOM it falls back to in-memory storage and no-ops browser hooks, so it’s safe to import in server-rendered apps.
Terminal window
npm install @narrato-app/sdk-web
import * as narrato from '@narrato-app/sdk-web';
narrato.init('<your-write-key>', { host: '<your-api-url>' });
narrato.track('signup_completed', { plan: 'free' });

No bundler? The IIFE build exposes a global narrato with the same API:

<script src="https://unpkg.com/@narrato-app/sdk-web@0.1.3/dist/index.global.js" integrity="sha384-VfaFigAs9+sz2beCxFuZXSgi6ajbUl6ElD/6kXZhyYTmURyRGY9DTXTGWKWB5/VW" crossorigin="anonymous"></script>
<script>
narrato.init('<your-write-key>', { host: '<your-api-url>' });
</script>
Function Description
init(writeKey, options?) Initialize the shared client. Call once, early. Returns the client.
track(eventName, properties?) Queue a custom event.
identify(userId, traits?) Associate subsequent events with a known user; emits an identify event carrying the traits.
page(properties?) Track a page view. URL, path, title, referrer, and the session’s initial_referrer are captured automatically.
reset() Log-out: new anonymous id, new session.
flush() Send everything queued now (returns a promise).
shutdown() Remove timers/listeners (tests, hot reload). Queued events stay persisted.

Need multiple isolated clients (rare)? new NarratoClient({ writeKey, … }) from the same package.

Option Default Description
host https://api.narrato.app Narrato API base URL (no trailing slash).
flushAt 20 Flush as soon as this many events are queued.
flushInterval 10000 Also flush on this interval (ms).
sessionTimeout 1800000 Session renews after this much inactivity (ms).
autoPageviews true Track initial load + SPA navigations (pushState/replaceState/popstate).
maxQueueSize 1000 Offline buffer cap; oldest events are dropped beyond it.
appVersion "" Reported as app_version on every event.
debug false Log SDK activity to the console.

Identity. A random anonymous id is generated on first load and persisted. It’s sent as user_id until identify() supplies a real one, and always as device_id, so pre- and post-login activity can be joined. reset() rotates it.

Sessions. A session id persists across page loads and renews after sessionTimeout of inactivity. Every tracked event slides the window.

Delivery. Events queue locally and flush when flushAt is reached or every flushInterval, in batches of up to 500. Network errors, 429s, and 5xxs keep events queued and back off exponentially (1s → 30s cap); other 4xx responses drop the batch (retrying can’t fix a rejected payload). On pagehide/tab-hidden the queue is handed to navigator.sendBeacon, which survives page unload.

The write key travels as the X-Narrato-Write-Key header, and as a ?writeKey= query parameter on beacon requests, which can’t set headers. The API validates it and stamps every event with the owning project server-side. An invalid key gets a 401 and the batch is dropped; turn on debug: true and check the browser console if events aren’t arriving.

Auto-capture scope. Only page views are auto-captured. Clicks and form submissions are deliberately not auto-tracked: they generate noisy data and can capture sensitive input without consent controls. Track what matters explicitly, and see Privacy & PII before attaching user data.

Quota signals. When your workspace approaches its monthly event quota, ingestion responses carry X-Narrato-Usage-Warning / X-Narrato-Usage-Percent headers, and over-quota Free workspaces eventually receive 429s. The SDK treats those as retryable, so events queue rather than vanish. Details in Limits & billing.

Invalid events are dropped one at a time. track() checks each event against the API’s rules (event names: letters, digits, _, - and spaces; properties: at most 100 keys, 5 levels deep, 32 K characters of JSON, JSON-serializable). An event that breaks them is not queued, and a [narrato] warning in the console says why. It never takes other events down with it.

Page views redact credentials. Query and fragment parameters that look like secrets (token, access_token, key, password, …, and code/state on OAuth callbacks) are recorded as [redacted] in page_url, search and referrer. Hash routes like #/settings are kept, and a page view is recorded only when the URL actually changes.

Single-page apps. On an in-app navigation the referrer is the page being left (the browser’s document.referrer only describes how the visit arrived, so the first page view keeps it). Routers update the URL before they render the new page, so the page view waits up to 2 seconds for the new <title>; its URL and timestamp are still the navigation’s. A page whose title never changes is sent with it. If another navigation, or leaving the page, comes first, the page view is sent without a title rather than with the wrong one. Every page view also carries initial_referrer: the referrer the session arrived with, the same for each page of the visit, so traffic sources can be counted per session. It’s empty for direct visits, and for a session that starts from a page of your own site (after a logout, or a visit idle past sessionTimeout).

Known limitation. The offline queue is shared per-origin via localStorage, so no tab loses another’s unsent events. Two tabs flushing at the same instant can, in rare interleavings, duplicate a batch (or, rarer still, drop one tab’s write). Fine for analytics; flagged here for honesty.