Browser SDK Reference
TL;DR: Install
@featureflip/browser-sdk, callclient.boolVariation('flag-key', false), and you’re evaluating flags in the browser — context comes fromidentify().
The @featureflip/browser package is the framework-agnostic Featureflip client for browser applications — vanilla JS, Vue, Svelte, Solid, Lit, Alpine, jQuery, or anything else that runs in a <script> tag. (For React specifically, use @featureflip/react, which adds a provider, hooks, and StrictMode-safe lifecycle handling on top of this package.) Unlike the server SDKs, the Browser SDK is a client-side SDK: it receives pre-evaluated feature flag values from the Evaluation API rather than running an evaluator locally. This keeps the bundle small and prevents shipping your targeting rules to every visitor.
Real-time updates arrive via the browser’s native EventSource, which means authentication is passed through the URL query string (browsers prohibit setting custom Authorization headers on EventSource). The SDK handles the auth flow automatically; you just provide your client SDK key. After the initial snapshot, the server streams delta updates — only the flags that changed — and the SDK merges them into the local cache rather than replacing it. Flags removed via targeting rules or deleted in the dashboard are dropped from the snapshot in place.
The Browser SDK is singleton-by-construction: there is no public constructor, only the static factory FeatureflipClient.get(config). Every call with the same client key returns a handle pointing at one shared underlying client. This makes framework bindings, React StrictMode double-mounts, and per-component construction all harmless — they all resolve to one SSE connection and one flag store per key. See the Lifetime section for the full contract. identify() updates the active context (for example, after a user logs in) by closing the previous SSE stream and reopening with the new context. This is the right SDK for SPAs where you need percentage rollouts and targeting to react to login state in real time.
Requirements
Section titled “Requirements”- Any modern browser with
fetchandEventSourcesupport - Node.js 20.19.0+ (for build tooling)
Installation
Section titled “Installation”npm install @featureflip/browser-sdkQuick Start
Section titled “Quick Start”import { FeatureflipClient } from '@featureflip/browser-sdk';
const client = FeatureflipClient.get({ clientKey: 'your-client-sdk-key',});
await client.initialize();
const showBanner = client.boolVariation('show-banner', false);FeatureflipClient.get() is the only way to obtain a client — the public constructor was removed in v2.0. See Lifetime for details.
Configuration
Section titled “Configuration”FeatureflipClient.get(config: FeatureflipClientConfig)| Option | Type | Default | Description |
|---|---|---|---|
clientKey | string | (required) | Client SDK key from your project settings |
baseUrl | string | https://eval.featureflip.io | Evaluation API base URL |
context | Record<string, unknown> | {} | Initial evaluation context (user attributes) |
streaming | boolean | true | Enable SSE streaming for real-time updates |
initTimeout | number | 10000 | Timeout in ms for the initial evaluate request |
Initialization
Section titled “Initialization”Call initialize() before reading any flag values. This fetches all client-visible flags from the server and opens an SSE connection if streaming is enabled.
const client = FeatureflipClient.get({ clientKey: 'your-client-sdk-key',});
await client.initialize();Lifetime
Section titled “Lifetime”The Browser SDK is singleton-by-construction: you cannot new a client directly, and every FeatureflipClient.get(config) call with the same clientKey returns a handle pointing at one shared background client. This is intentional — a browser client maintains a long-lived SSE streaming connection and an in-memory flag store. Opening more than one per clientKey per page wastes bandwidth and causes flag updates to fan out unpredictably.
The factory refcounts the shared core:
- The first
get()for a given client key constructs the shared core and returns a handle with refcount1. - Subsequent
get()calls with the same key return a new handle and increment the refcount. Theconfigargument on repeat calls is ignored; if the resolved config differs meaningfully from the first call, a warning is logged. handle.close()decrements the refcount. When the refcount reaches zero, the shared core runs its real shutdown: closes the SSE stream and removes itself from the factory cache.- Double-closing the same handle is a no-op — the handle tracks its own disposed state and will not double-decrement.
- A different client key always constructs an independent shared core.
This makes the SDK safe under any lifetime pattern:
- React StrictMode: mount → cleanup → remount cycles return new handles over the same core. The cleanup’s
close()drives the refcount from 2 → 1 (not 0), so the SSE connection stays alive across the simulated remount. This is exactly what@featureflip/react’sFeatureflipProviderrelies on. - Per-component construction: framework bindings can call
get()inside component render without worrying about duplicating SSE connections. Each call allocates a fresh handle, not a new network connection. - Micro-frontend architectures: independent React/Vue/Svelte islands sharing a client key on the same page share one underlying connection.
Use FeatureflipClient.forTesting(flags) to obtain a standalone test client that is not registered in the factory cache — each call returns an independent instance with no background connections.
Evaluating Flags
Section titled “Evaluating Flags”All variation methods accept a flag key and a default value. The default is returned if the flag is missing or has an unexpected type.
boolVariation(key, defaultValue)
Section titled “boolVariation(key, defaultValue)”const enabled = client.boolVariation('dark-mode', false);stringVariation(key, defaultValue)
Section titled “stringVariation(key, defaultValue)”const color = client.stringVariation('button-color', 'blue');numberVariation(key, defaultValue)
Section titled “numberVariation(key, defaultValue)”const limit = client.numberVariation('rate-limit', 100);jsonVariation<T>(key, defaultValue)
Section titled “jsonVariation<T>(key, defaultValue)”const config = client.jsonVariation<{ sidebar: boolean }>('ui-config', { sidebar: true });Identifying Users
Section titled “Identifying Users”Call identify() to re-evaluate all flags with a new user context — typically after login or when user attributes change. Emits change events for any flags whose values changed.
await client.identify({ user_id: '123', plan: 'pro' });Events
Section titled “Events”on(event, handler)
Section titled “on(event, handler)”Subscribe to lifecycle events.
| Event | Description |
|---|---|
'ready' | Fired after initialize() completes |
'change' | Fired when flag values change (receives a FlagChanges object) |
'error' | Fired on streaming or network errors |
client.on('change', (changes) => { console.log('Flags changed:', changes);});off(event, handler)
Section titled “off(event, handler)”Unsubscribe from events.
Cleanup
Section titled “Cleanup”close()
Section titled “close()”Decrements the refcount on the shared core. When the last handle for a given client key is closed, the shared core closes the SSE connection and removes itself from the factory cache. Double-closing the same handle is a no-op.
client.close();Testing
Section titled “Testing”Use FeatureflipClient.forTesting() to create a client with predetermined flag values — no network calls, no initialization needed.
const client = FeatureflipClient.forTesting({ 'show-banner': true, 'button-color': 'blue',});
client.boolVariation('show-banner', false); // trueclient.stringVariation('button-color', 'red'); // 'blue'See also
Section titled “See also”- JavaScript Quickstart — get started in under 5 minutes
- React SDK — React-specific bindings with provider and hooks
- Targeting & Segments — control which users see which variation
- Evaluation API — REST API reference
- SDK Overview — compare server-side and client-side SDKs