Skip to main content
@traffical/js-client is the framework-agnostic browser SDK. It fetches the config bundle, resolves parameters in the browser, tracks exposure events, and manages anonymous-user identity automatically. If you’re using React or Svelte, prefer the React SDK or Svelte SDK — they wrap this client with framework-native APIs.

Installation

Setup

Use an SDK key (traffical_sk_..., scopes sdk:read+sdk:write) in browser code. SDK keys can fetch the bundle and send events but cannot modify configuration — safe to ship in client bundles.

Resolving parameters

Both getParams(context, defaults) and decide(context, defaults) take context first, defaults second, and both are synchronous. In the browser you can usually omit the unit key — the SDK fills it from the auto-generated stable ID (see below).
The legacy object-bag form — getParams({ context, defaults }) / decide({ context, defaults }) — still works but is deprecated. Prefer the positional form.
An exposure event is emitted automatically the first time a user/assignment combination is resolved in a session.

Anonymous users and identify

Browser-side experiments often need to resolve parameters before a user is logged in. To make that work without you having to think about it, the browser SDK auto-generates a stable UUID on first visit, stores it in localStorage (with a cookie fallback), and fills the project’s unit-key slot with that value whenever the caller doesn’t pass one. So if the project’s unit key is userId, the SDK effectively passes context.userId = "<auto-generated-uuid>" on every pre-login resolution. Bucketing works, exposures fire, the user gets a stable assignment for as long as their localStorage persists. When the user logs in, call identify(realUserId):
This overwrites the stored stable ID with the real userId. Every subsequent resolution uses the new value.
Bucketing changes at login. The pre-login bucket and the post-login bucket are computed from different inputs (hash(uuid + layerId) vs hash("user_789" + layerId)) — they almost always land in different allocations. A user who saw the treatment variant while anonymous may see the control variant after logging in, and vice versa.This is unavoidable with a single unit of randomization per project. There’s no “anonymous-to-identified” continuity layer: the SDK doesn’t remember which bucket the stable ID was in and remap the userId to match. If you need user-stable assignments to persist across login, do the experiment on logged-in users only — gate the policy with a condition that the unit key matches a real user-ID format, or have your backend force identify() before the SDK ever resolves the parameter.
You can read the current stable ID with traffical.getStableId(). After identify(), this returns the value you passed in.

Tracking events

track(event, properties?, options?) — the event name first, an optional properties payload second, and an options bag (unitKey, decisionId, value, values, eventTimestamp) third. In the browser unitKey defaults to the current stable ID, so you rarely pass it:
Events are batched in memory and flushed in the background.

Lifecycle

createTrafficalClient (async) awaits the first bundle fetch. When you can’t await at construction, use createTrafficalClientSync and await readiness before the first resolution:
Force a flush of queued events (e.g. after a critical conversion, before navigating):
Tear the client down with the single teardown verb, close(). It stops the background refresh and awaits a final event flush before returning (on page unload it falls back to sendBeacon so the last batch still ships):
destroy() still exists but is deprecated — prefer close(), which awaits the final flush.

Options

Deprecated option aliases. Earlier releases used eventBatchSize, eventFlushIntervalMs, and a single requestTimeoutMs. These still work and forward to the canonical names above (batchSize, flushIntervalMs, and the three-way timeout split). requestTimeoutMs is honored as the legacy fallback for any of configTimeoutMs / eventsTimeoutMs / resolveTimeoutMs that you don’t set explicitly. Prefer the canonical names — the aliases will be removed in a future major.

Bundle caching

The browser SDK caches the bundle in memory. The first page load fetches it; subsequent loads benefit from HTTP caching at the CDN edge (Cache-Control: public, max-age=60, must-revalidate with ETag). On warm loads, resolution is available immediately.

Plugins

The browser SDK has a small plugin system that powers some of Traffical’s other tools:

Framework SDKs

If you’re using React or Svelte, use the framework-specific SDKs instead: Both wrap @traffical/js-client and expose the same plugin system.