Everything below lives on the default export of oakdata-js. Import it once and call methods anywhere - calls made before init() are queued and replayed, so ordering never matters.
import oak from 'oakdata-js'oak.init(key, options)
Boots the tracker. Call it once with your project key. Returns the live API instance (also stored on window.oak), or null during SSR / prerender. Calling init twice is a no-op - the first call wins.
oak.init(process.env.NEXT_PUBLIC_OAK_KEY!, {
api_host: process.env.NEXT_PUBLIC_OAK_HOST,
})The options object accepts:
| Option | Type | Description |
|---|---|---|
api_host | string | Base URL events are sent to. Defaults to the current origin - set it to https://oakdata.co, or to your own subdomain when using the managed reverse proxy. |
autocapture | boolean | Autocapture clicks, form submits, input changes, and copies. Default true. See autocapture. |
capture_pageview | boolean | Automatically fire $pageview on load and on every client-side route change. Default true. |
capture_inputs | boolean | Include input and form-field values in autocapture events (passwords are never captured). Default false - only field names and whether they were filled. |
outbound | boolean | Fire $outbound_click when a link to another domain is clicked. Default true. |
declarative | boolean | Honor data-oak-event / data-oak-prop-* attributes for markup-only event tracking. Default true. See autocapture. |
session_timeout_ms | number | Inactivity window before a new session starts. Default 1800000 (30 minutes). |
respect_dnt | boolean | Honor the browser's Do Not Track signal. When true and DNT is enabled, the tracker disables itself entirely. Default false. |
property_denylist | string[] | Property keys stripped from every event before it's sent - useful for scrubbing PII or noisy fields. Default []. |
before_send | fn | fn[] | A hook (or array of hooks) run on each event just before it's queued. Return the event to send it, or null/false to drop it. Hooks run in order; the first to drop wins. |
loaded | (oak) => void | Called once, after the tracker is fully wired up. |
debug | boolean | Log internal activity to the console. Default false. |
before_send example
Drop events from a noisy path and redact a property:
oak.init(KEY, {
before_send: (event) => {
if (event.context.page.path.startsWith('/admin')) return null
if (event.properties.email) event.properties.email = '[redacted]'
return event
},
})Content Security Policy
If your site sends a Content-Security-Policy header, allow your api_host in connect-src - or the browser silently blocks every event:
connect-src 'self' https://oakdata.co;If you load the SDK from a CDN via a script tag, also allow that origin in script-src (e.g. https://esm.sh or https://cdn.jsdelivr.net). The npm install needs no script-src change.
Capturing events
oak.capture(name, properties?)
Records a custom event. oak.track() is an exact alias. Names are free-form strings; properties are any JSON-serializable object.
oak.capture('signup_completed', { plan: 'pro', seats: 5 })oak.page(properties?)
Manually records a $pageview. Rarely needed - with capture_pageview on (the default), pageviews fire automatically. Use it for virtual pageviews, e.g. a modal you treat as a screen.
oak.page({ section: 'onboarding' })Identity
See identity resolution for how anonymous and identified activity are stitched together.
oak.identify(userId, traits?)
Links the current anonymous visitor to a known user id, merging their prior anonymous activity into the identified profile. Optionally pass traits to set in the same call. Afterwards, getDistinctId() returns the user id.
oak.identify('user_8f3a', { email: 'sam@acme.com', plan: 'pro' })oak.alias(newId)
Records an alias linking the current distinct id to newId and switches the stored id to it. Use it to merge two ids you control - for example, tying a server-generated id to the browser's anonymous id.
oak.alias('user_8f3a')oak.set(traits) / oak.setOnce(traits)
Sets persistent traits on the current person. set overwrites existing keys; setOnceonly writes keys that aren't already present - good for first-touch attributes like signup date.
oak.set({ plan: 'enterprise' })
oak.setOnce({ first_seen_plan: 'free', signup_source: 'docs' })Super properties
Super properties are merged into every subsequent event and persisted in local storage, so they survive reloads.
oak.register(props) / oak.unregister(key)
oak.register({ app_version: '2.4.0', workspace: 'acme' })
oak.unregister('workspace')Groups
oak.group(type, id, traits?)
Associates the current person with a group - an account, company, or any other entity many users share. Subsequent events carry the group association.
oak.group('company', 'acme-co', { name: 'Acme', plan: 'enterprise' })Consent & lifecycle
oak.reset()
Clears all stored ids, traits, super properties, groups, and the queued event buffer, then generates a fresh anonymous id. Call it on sign-outso the next visitor on a shared device isn't attributed to the previous user.
oak.reset()oak.opt_out() / oak.opt_in()
opt_out() stops all sending, clears the pending queue, and persists the choice across reloads. opt_in() re-enables tracking. Use these to back a consent banner.
if (userDeclinedAnalytics) oak.opt_out()
else oak.opt_in()oak.flush(useBeacon?)
Sends queued events immediately instead of waiting for the next batch. Returns a promise. Pass true to use navigator.sendBeacon, which survives page unload (the SDK already does this internally on pagehide).
await oak.flush()Getters
| Method | Type | Description |
|---|---|---|
oak.getDistinctId() | string | null | The current distinct id - the identified user id once identify() has run, otherwise the anonymous id. null before init(). |
oak.getSessionId() | string | null | The current session id. null before init(). |
The instance returned by oak.init() (and window.oak after boot) also exposes getFirstTouch() and getLastTouch() for first- and last-touch attribution.
Autocaptured events
With autocapture and capture_pageview on, the SDK emits these without any code from you:
| Event | Type | Description |
|---|---|---|
$pageview | auto | Page load + SPA route change. |
$click | auto | Any click, with the resolved actionable element. |
$rage_click | auto | Three or more rapid clicks in the same spot. |
$dead_click | auto | A click that produced no DOM change or navigation. |
$outbound_click | auto | A click on a link to another host. |
$form_submit | auto | A form submission (password fields excluded). |
$input_change | auto | An input/select/textarea value change (value masked by default). |
$copy | auto | Text copied to the clipboard. |
See autocapture & data attributes for opting elements in or out, and events & properties for the properties attached to every event.
Performance & error events
The SDK also watches page performance and uncaught errors out of the box - no setup, and independent of autocapture. A slipped Core Web Vital or an error spiking on one route shows up right alongside behavioural data.
| Event | Type | Description |
|---|---|---|
$web_vital | auto | A Core Web Vital reading - LCP, INP, or CLS- reported as it's finalized for the page. |
$error | auto | An uncaught JavaScript error, with its message, source, and stack. |
$unhandled_rejection | auto | A promise rejection with no handler. |
$performance / $paint | auto | Navigation and paint timing for the page, captured at pageview time. |
$long_task | auto | A main-thread task long enough to block interaction. |
$page_leave | auto | Fired when the visitor leaves the page, closing out engagement time. |
Errored sessions are flagged in the sessions and replaylists, so you can jump from “something broke” to the exact recording.