Browser SDK

The core tracking engine, in vanilla JavaScript and TypeScript.

@pivolio/browser is the engine every other binding wraps. It has zero runtime dependencies and ships ESM, CommonJS, an IIFE bundle, and types.

Install

$npm install @pivolio/browser

Initialize

1import { init, page } from '@pivolio/browser';
2
3init({
4 writeKey: 'wk_live_xxxxxxxxxxxx',
5 apiUrl: 'https://api.pivolio.com',
6});
7
8page();

init() returns the Tracker it built. Calling it twice warns and returns the existing instance rather than creating a second one — so an accidental double init won’t double-count your traffic. Every option is documented in Configuration.

Methods

The module exports a singleton API. Every method below is also a method on the Tracker class, which you can construct directly with new Tracker(config) if you need more than one instance.

MethodSignatureWhat it does
init(config: SDKConfig) => TrackerBuild the singleton and start tracking
page() => voidEmit a page_view for the current URL
track(name, data?: { properties?, traits? }) => voidEmit a custom event
identify(user: Traits & { user_id?: string }) => voidAttach a known identity
trackPurchase(data: CommerceData) => voidEmit purchase
trackAddToCart(data: CommerceData) => voidEmit add_to_cart
trackBeginCheckout(data: CommerceData) => voidEmit begin_checkout
trackLead(data?: TrackEventData) => voidEmit lead
setConsent(consent: Consent) => voidUpdate Consent Mode v2 state
optOut / optIn() => voidDurable, persisted opt-out
hasOptedOut() => booleanWhether this visitor opted out
getAnonymousId() => string | undefinedCurrent anonymous_id
getSessionId() => string | undefinedCurrent session id
getUserId() => string | null | undefinedKnown user_id, if identified
getLinkerParam() => string_ut=… for cross-domain links
debug(enabled: boolean) => voidToggle debug logging at runtime
flush() => Promise<void>Send buffered events now
reset() => voidForget the identity — see the warning below
destroy() => voidTear down listeners without rotating identity
getInstance() => Tracker | nullThe singleton, or null
versionstringThe SDK version

Two methods exist only on the Tracker class and have no singleton equivalent: getConsent() returns the current Consent Mode state, and isInitialized() reports whether the tracker actually started (it returns false when respectDNT disabled it).

Tracking events

Properties are nested under properties. This is the single most common mistake — a top-level bag is silently not read as event properties:

1import { track } from '@pivolio/browser';
2
3track('order_completed', {
4 properties: { order_id: 'ord_123', revenue: 99.5, currency: 'USD' },
5});

You can attach PII on the same call via traits:

1track('lead_submitted', {
2 properties: { form: 'demo-request' },
3 traits: { email: 'jane@acme.com' },
4});

Event names must match ^[a-zA-Z0-9_.]{1,128}$, and the property bag is capped at 64 KB of JSON. Violate either and the SDK drops the event client-side with an error in the console rather than sending something the collector will reject.

Commerce helpers — prefer these

trackPurchase, trackAddToCart and trackBeginCheckout take a CommerceData object and rewrite its well-known keys into the exact property names the backend enricher reads:

1import { trackPurchase } from '@pivolio/browser';
2
3trackPurchase({ value: 99.5, currency: 'USD', orderId: 'ord_123' });
  • value — becomes properties.value, the revenue signal.
  • currency — becomes properties.currency.
  • orderId — becomes properties.order_id.

That last mapping is the reason to prefer the helpers over raw track(). The enricher reads order_id, not orderId; ad platforms use it as the deduplication key, so a camelCase typo means the same purchase can be counted twice by Meta and by Google Ads. Any extra keys you pass are forwarded unchanged.

The helpers emit purchase, add_to_cart, begin_checkout and lead. If your reporting is built around order_completed or lead_submitted instead, use track() with the helper’s property shape — both names carry the same conversion weight.

identify()

1import { identify } from '@pivolio/browser';
2
3identify({ user_id: 'user_456', email: 'jane@acme.com' });

Send raw email and phone. Pivolio normalizes and hashes them server-side so every destination receives byte-identical digests — hashing them yourself is the fastest way to quietly destroy your match rates.

identify() only emits an event when something actually changed: a new user_id, or a trait whose value differs from what it already holds. Calling it on every page load with the same payload is therefore free and idiomatic — the repeats are skipped rather than inflating your event volume. Traits accumulate across calls, so a later identify({ phone }) keeps the email from earlier.

reset() and destroy()

The singleton reset() also calls destroy() and nulls the instance. After calling it you must call init() again or every subsequent track() will warn and no-op. The Tracker class method behaves differently: tracker.reset() rotates the identity and leaves the tracker running.

Use reset() on logout, to give the browser a fresh anonymous_id and session and clear accumulated traits:

1import { init, reset } from '@pivolio/browser';
2
3reset();
4init({ writeKey: 'wk_live_xxxxxxxxxxxx', apiUrl: 'https://api.pivolio.com' });

Use destroy() when you want to tear down listeners without touching identity — on unmount, or before a hot reload. It persists anything still buffered, so a later init() replays those events instead of losing them.

Next steps