SignaKitdocs
SDKs

Browser

Full API reference for @signakit/flags-browser — client-side feature flags with CDN-fetched config, local evaluation, and sessionStorage exposure deduplication.

Browser SDK

Package: @signakit/flags-browser Registry: npm Environments: Browser (ESM), bundler required (Webpack, Vite, Turbopack)

The browser SDK fetches your flag config from the SignaKit CDN once on initialization, then evaluates all flags locally — no network call per decide(). Exposure events are deduplicated via sessionStorage so each user only fires one $exposure per flag per browser session.

Using React? Use @signakit/flags-react instead — it wraps this package with SignaKitProvider, useFlag, and FlagGate, handling initialization, loading state, and context propagation. See the React SDK →


Installation

npm install @signakit/flags-browser

Initialization

lib/signakit.js
import { createInstance } from '@signakit/flags-browser'

const client = createInstance({
  sdkKey: 'sk_prod_yourOrgId_yourProjectId_random',
})

if (!client) {
  console.error('Invalid SDK key format')
} else {
  const { success, reason } = await client.onReady()
  if (!success) {
    console.error('Failed to load flags:', reason)
  }
}

createInstance() returns null for malformed SDK keys — always null-check before calling onReady().

Config options

OptionTypeDefaultDescription
sdkKeystringrequiredYour SignaKit browser SDK key
timeoutnumber5000Config fetch timeout in milliseconds
retriesnumber3Fetch retries with exponential backoff

onReady()

Fetches the flag config from the SignaKit CDN. Resolves when the config is ready or all retries are exhausted.

const { success, reason } = await client.onReady()
// success: true  → client.createUserContext() is safe to call
// success: false → reason explains the failure; decide() returns null

Subsequent calls to onReady() resolve immediately — the config is cached in memory for the instance lifetime.

ETag-based refresh — the SDK sends If-None-Match on background refreshes. The CDN returns 304 Not Modified when the config hasn't changed, keeping bandwidth minimal.

Circuit breaker — if the CDN is unreachable after all retries, onReady() resolves with success: false. The client serves the last cached config from sessionStorage if available. decide() returns null when there is no cached config.


Creating a user context

const userContext = client.createUserContext('user-123', {
  plan: 'pro',
  country: 'US',
  betaTester: true,
})
if (!userContext) {
  // userId was empty string
}
ParameterTypeDescription
userIdstringStable unique identifier. Must be non-empty — returns null for empty string.
attributesRecord<string, string | number | boolean>Matched against audience targeting rules.

Pass $userAgent: navigator.userAgent to enable bot detection — bots receive the off variation and skip event tracking.


decide(flagKey)Decision | null

const decision = userContext.decide('new-checkout')

if (decision?.enabled) {
  renderNewCheckout()
}

Returns null when the flag doesn't exist, the client isn't ready, or the user doesn't match any rule. Treat null as the default/off state.

FieldTypeDescription
flagKeystringThe key you passed in
enabledbooleanWhether the flag is on for this user
variationKeystringAssigned variation: "control", "treatment", or a custom key
ruleKeystringThe rule that matched this user

decideAll()Record<string, Decision>

const decisions = userContext.decideAll()
// Only flags where the user matches a rule are included

decideAll() fires an $exposure event for every flag the user is bucketed into. Call it once after onReady() — not on route changes or re-renders.


trackEvent(eventName, properties?)

await userContext.trackEvent('purchase_completed', {
  value: 49.99,
  plan: 'pro',
})
ParameterTypeDescription
eventNamestringMatches an event definition in your SignaKit project
propertiesRecord<string, string | number | boolean>Optional metadata

Fire and forget — returns Promise<void>. Errors are logged but never thrown.


Exposure deduplication

Uses sessionStorage to track already-exposed flags in the current browser session.

sessionStorage key: sk_exposed_{flagKey}_{userId}

The first decide('my-flag') call for a userId fires $exposure and sets the key. All subsequent calls within the same tab session are silent. Opening a new tab starts a fresh session.


TypeScript types

import type {
  SignaKitBrowserClient,
  SignaKitUserContext,
  BrowserDecision,
  BrowserDecisions,
} from '@signakit/flags-browser'

Anti-patterns

PatternProblemFix
decide() inside a React render functionFires evaluation on every render; bypasses Provider dedupUse @signakit/flags-react
createInstance() called multiple timesEach instance fetches config independentlyCreate one instance at app init and export it
Not awaiting onReady() before decide()Returns null if config hasn't loadedAwait onReady() before first evaluation
decideAll() on every route changeFires a fresh exposure set on each navigationCall once after onReady(), cache in state

Last updated on

On this page