Skip to content

Browser SDK Reference

TL;DR: Install @featureflip/browser-sdk, call client.boolVariation('flag-key', false), and you’re evaluating flags in the browser — context comes from identify().

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.

View source on GitHub

  • Any modern browser with fetch and EventSource support
  • Node.js 20.19.0+ (for build tooling)
Terminal window
npm install @featureflip/browser-sdk
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.

FeatureflipClient.get(config: FeatureflipClientConfig)
OptionTypeDefaultDescription
clientKeystring(required)Client SDK key from your project settings
baseUrlstringhttps://eval.featureflip.ioEvaluation API base URL
contextRecord<string, unknown>{}Initial evaluation context (user attributes)
streamingbooleantrueEnable SSE streaming for real-time updates
initTimeoutnumber10000Timeout in ms for the initial evaluate request

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();

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 refcount 1.
  • Subsequent get() calls with the same key return a new handle and increment the refcount. The config argument 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’s FeatureflipProvider relies 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.

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.

const enabled = client.boolVariation('dark-mode', false);
const color = client.stringVariation('button-color', 'blue');
const limit = client.numberVariation('rate-limit', 100);
const config = client.jsonVariation<{ sidebar: boolean }>('ui-config', { sidebar: true });

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' });

Subscribe to lifecycle events.

EventDescription
'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);
});

Unsubscribe from events.

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();

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); // true
client.stringVariation('button-color', 'red'); // 'blue'