@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
npm install @traffical/js-client
Setup
import { createTrafficalClient } from "@traffical/js-client";
const traffical = await createTrafficalClient({
orgId: "org_acme",
projectId: "proj_marketplace",
env: "production",
apiKey: "traffical_sk_...",
});
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
const params = traffical.getParams({
context: { userId: "user_789" },
defaults: {
"homepage.hero_headline": "Welcome back",
"homepage.show_banner": true,
},
});
document.querySelector("h1")!.textContent = params["homepage.hero_headline"];
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):
traffical.identify("user_789");
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
traffical.track("signup", { unitKey: "user_789" });
traffical.track("purchase", {
unitKey: "user_789",
properties: { order_total: 29.99, currency: "USD" },
});
Events are batched in memory and flushed in the background.
Options
| Option | Type | Default | Description |
|---|
orgId | string | required | Organization ID |
projectId | string | required | Project ID |
env | string | required | Environment name |
apiKey | string | required | SDK key (traffical_sk_..., scopes sdk:read+sdk:write) — browser-safe |
baseUrl | string | https://sdk.traffical.io | SDK API base URL |
refreshIntervalMs | number | 60000 | Bundle refresh interval |
localConfig | ConfigBundle | — | Embedded bundle for cold-start |
evaluationMode | "bundle" | "server" | "bundle" | See how it works |
attributionMode | "cumulative" | "decision" | "cumulative" | Track-event attribution strategy |
trackDecisions | boolean | true | Emit decision events alongside exposures |
disableAutoStableId | boolean | false | Disable auto-generation of stableId |
storage | StorageProvider | LocalStorageProvider | Persistence for the stable ID |
plugins | TrafficalPlugin[] | [] | Plugins (DOM bindings, redirect tests, devtools) |
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:
| Plugin | Purpose |
|---|
| DOM bindings | Auto-apply parameter values to DOM elements (used by the visual editor) |
| Redirect tests | URL split-testing — redirect users to different URLs based on assignment |
| Warehouse-native logger | Forward assignment events to your warehouse |
| DevTools | Live SDK inspection in the browser (used by the DevTools bookmarklet) |
import { createTrafficalClient, createDOMBindingPlugin } from "@traffical/js-client";
const traffical = await createTrafficalClient({
orgId, projectId, env, apiKey,
plugins: [createDOMBindingPlugin()],
});
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.