> 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.

# Configuration

The same `SDKConfig` object is accepted by `init()` in
[`@pivolio/browser`](/documentation/sd-ks/browser), by `new Tracker(config)`, and by the `config`
prop on [`PivolioProvider`](/documentation/sd-ks/react). Only `writeKey` and `apiUrl` are
required.

```ts
init({
  writeKey: 'wk_live_xxxxxxxxxxxx',
  apiUrl: 'https://api.pivolio.com',
  autoPageView: true,
  trackSPANavigation: true,
});
```

> **Note**
>
> Every option is **init-time**. Changing a value after the tracker exists has no
> effect. The two things you can change at runtime have methods:
> `tracker.setConsent(...)` and `tracker.debug(...)`.

## Required

**`writeKey`** `string` — required

Your workspace's write key, sent as the `X-Write-Key` header. Find it under
**Settings → API keys**. Write keys can only ingest events — they can't read
data or change settings — so they're safe to ship in browser code.

---

**`apiUrl`** `string` — required

The collector base URL, e.g. `https://api.pivolio.com`. Point this at your own
subdomain to beat ad-blockers and Safari ITP — see
[First-party domains](/documentation/guides/first-party-domains).

---

## Identity and storage

**`cookieDomain`** `string`

Cookie domain, e.g. `.example.com`. Set this when you track across
subdomains — without it, `shop.example.com` and `www.example.com` mint
separate visitors. For separate registrable domains use
[the cross-domain linker](/documentation/sd-ks/identity-consent) instead.

---

**`cookiePath`** `string` — default: /

Cookie path.

---

**`storagePrefix`** `string` — default: \_\_ut\_

Prefix for every cookie and storage key the SDK writes. Change it only to avoid
a collision with another script on the page; changing it on a live site orphans
the existing identity and every visitor is counted as new.

---

**`secureCookies`** `boolean` — default: true

Set the `Secure` flag, restricting cookies to HTTPS. Turn it off only for local
development over plain HTTP.

---

**`anonymousIdTTL`** `number` — default: 365

Anonymous ID cookie lifetime, in **days**.

---

**`attributionTTL`** `number` — default: 30

How long captured UTMs and click IDs persist, in **days**. This is your
client-side attribution window; the server applies per-platform click expiry on
top of it, so raising this beyond 90 days buys little.

---

**`sessionTimeout`** `number` — default: 30

Inactivity before a new session starts, in **minutes**.

---

## Behavior

**`autoPageView`** `boolean` — default: false

Fire a `page_view` automatically when the tracker initializes. If you call
`page()` yourself, leave this off or you'll count every landing twice.

---

**`trackSPANavigation`** `boolean` — default: false

Emit a page view on `history.pushState`/`replaceState` and `popstate`. Enable
it for a single-page app that doesn't reload between routes.

On Next.js, use [`<PivolioPageView />`](/documentation/sd-ks/next-js) instead — it's App
Router-aware, and combining the two double-counts navigations.

---

**`testMode`** `boolean` — default: false

Stamp `test_mode: true` on every event. This excludes them from your billable
usage and lets you filter them out of the events list.

It does **not** stop delivery and does **not** exclude them from reporting —
test events still dispatch to connected destinations and still count in your
dashboards. Use a separate [workspace](/documentation/core-concepts/workspaces-write-keys) to isolate
staging, not this flag.

---

**`debug`** `boolean` — default: false

Verbose console logging. Toggle it at runtime with `debug(true)`.

---

## Privacy

**`respectDNT`** `boolean` — default: false

When the browser sends Do Not Track, disable the tracker entirely — it never
initializes, and `isInitialized()` returns `false`.

---

**`consent`** `Consent`

Initial Google Consent Mode v2 state:
`{ ad_storage, analytics_storage, ad_user_data, ad_personalization }`, each
`'granted'` or `'denied'`. Unset is treated as not-denied. See
[Identity and consent](/documentation/sd-ks/identity-consent) for what each category gates.

---

## Transport

**`batching`** `boolean` — default: true

Buffer events and send them via `/batch`. Turning this off sends each event
immediately via `/track` — simpler to debug, materially more requests.

---

**`batchSize`** `number` — default: 10

Events buffered before an immediate flush.

---

**`flushInterval`** `number` — default: 2000

Maximum time to hold a partial batch, in **milliseconds**.

---

**`requestTimeout`** `number` — default: 10000

Per-request timeout, in **milliseconds**.

---

**`maxRetries`** `number` — default: 3

Total attempts before falling back to `sendBeacon`, with exponential backoff
between them. 4xx responses other than 429 are permanent and stop retrying
immediately — retrying a rejected payload only wastes the visitor's battery.

---

## Callbacks

**`onEventSent`** `(event: { eventId: string; eventName: string }) => void`

Fires after an event is accepted by the collector.

---

**`onEventError`** `(error: Error, event: { eventId: string; eventName: string }) => void`

Fires only after **every** delivery path has failed: retried fetch, then
`sendBeacon`, then persisting to the offline queue. An event that lands in the
offline queue is not lost — it is retried on the next page load — so this
callback firing means the event is genuinely gone.

---

## Next steps

* [Identity and consent](/documentation/sd-ks/identity-consent) — storage keys, TTLs, the
  linker and the opt-out API.
* [Browser SDK](/documentation/sd-ks/browser) — the method surface.