> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pivolio.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pivolio.com/_mcp/server.

# Browser SDK

`@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

```bash
npm install @pivolio/browser
```

#### Script tag

```html
<script async src="https://unpkg.com/@pivolio/browser@1/dist/browser.browser.js"></script>
```

See [Script tag](/documentation/sd-ks/script-tag) for the async command queue.

## Initialize

```ts
import { init, page } from '@pivolio/browser';

init({
  writeKey: 'wk_live_xxxxxxxxxxxx',
  apiUrl: 'https://api.pivolio.com',
});

page();
```

`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](/documentation/sd-ks/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.

| Method               | Signature                                         | What it does                                    |
| -------------------- | ------------------------------------------------- | ----------------------------------------------- |
| `init`               | `(config: SDKConfig) => Tracker`                  | Build the singleton and start tracking          |
| `page`               | `() => void`                                      | Emit a `page_view` for the current URL          |
| `track`              | `(name, data?: { properties?, traits? }) => void` | Emit a custom event                             |
| `identify`           | `(user: Traits & { user_id?: string }) => void`   | Attach a known identity                         |
| `trackPurchase`      | `(data: CommerceData) => void`                    | Emit `purchase`                                 |
| `trackAddToCart`     | `(data: CommerceData) => void`                    | Emit `add_to_cart`                              |
| `trackBeginCheckout` | `(data: CommerceData) => void`                    | Emit `begin_checkout`                           |
| `trackLead`          | `(data?: TrackEventData) => void`                 | Emit `lead`                                     |
| `setConsent`         | `(consent: Consent) => void`                      | Update Consent Mode v2 state                    |
| `optOut` / `optIn`   | `() => void`                                      | Durable, persisted opt-out                      |
| `hasOptedOut`        | `() => boolean`                                   | Whether this visitor opted out                  |
| `getAnonymousId`     | `() => string \| undefined`                       | Current `anonymous_id`                          |
| `getSessionId`       | `() => string \| undefined`                       | Current session id                              |
| `getUserId`          | `() => string \| null \| undefined`               | Known `user_id`, if identified                  |
| `getLinkerParam`     | `() => string`                                    | `_ut=…` for cross-domain links                  |
| `debug`              | `(enabled: boolean) => void`                      | Toggle debug logging at runtime                 |
| `flush`              | `() => Promise<void>`                             | Send buffered events now                        |
| `reset`              | `() => void`                                      | Forget the identity — **see the warning below** |
| `destroy`            | `() => void`                                      | Tear down listeners without rotating identity   |
| `getInstance`        | `() => Tracker \| null`                           | The singleton, or `null`                        |
| `version`            | `string`                                          | The 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:

```ts
import { track } from '@pivolio/browser';

track('order_completed', {
  properties: { order_id: 'ord_123', revenue: 99.5, currency: 'USD' },
});
```

You can attach PII on the same call via `traits`:

```ts
track('lead_submitted', {
  properties: { form: 'demo-request' },
  traits: { email: 'jane@acme.com' },
});
```

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:

```ts
import { trackPurchase } from '@pivolio/browser';

trackPurchase({ 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.

> **Note**
>
> 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()

```ts
import { identify } from '@pivolio/browser';

identify({ 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()

> **Warning**
>
> 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:

```ts
import { init, reset } from '@pivolio/browser';

reset();
init({ 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

* [Configuration](/documentation/sd-ks/configuration) — every option, default and unit.
* [Identity and consent](/documentation/sd-ks/identity-consent) — cookies, the linker, opt-out.
* [React](/documentation/sd-ks/react) and [Next.js](/documentation/sd-ks/next-js) — the framework bindings.