Skip to content

JavaScript / TypeScript SDK Reference

TL;DR: Install @featureflip/sdk, call client.boolVariation('flag-key', { user_id: 'u-123' }, false), and you’re evaluating flags — synchronous, never throws.

The @featureflip/js package is the foundation of the Featureflip JavaScript / TypeScript SDK family. It evaluates feature flags locally against an in-memory store, opens an SSE stream for real-time updates, and ships full TypeScript type definitions out of the box. Variation methods are synchronous and never throw — passing the wrong type, hitting a missing flag, or tripping over a network failure all return your supplied default value.

The package is the universal layer; specific runtimes get their own thin wrapper that handles platform quirks. Use @featureflip/node on the server (it sets User-Agent and uses Node’s fetch/undici), @featureflip/browser in the browser (it routes around forbidden fetch headers and uses EventSource with query-string auth), and @featureflip/react for React components (it adds FeatureflipProvider, useFlag, and StrictMode-safe lifecycle handling). All four packages share the same evaluation engine and event-tracking pipeline, so flag values are identical across runtimes for the same context.

Initialization is idempotent: calling initialize() twice returns the same in-flight promise rather than re-fetching, and close() is safe to call multiple times. The client tracks evaluation events automatically for targeting rules and percentage rollouts, batching them and flushing on a configurable interval so you never block a render or a request to record an impression.

View source on GitHub

  • Node.js 20.19.0+ (for Node.js usage) or a modern browser
  • TypeScript 5.0+ (optional, for type definitions)
Terminal window
npm install @featureflip/sdk

The SDK ships with dual platform support. Use the appropriate platform factory for your environment.

Node.js:

import { FeatureflipClient, createNodePlatform } from '@featureflip/sdk';

Browser:

import { FeatureflipClient, createBrowserPlatform } from '@featureflip/sdk/browser';

The FeatureflipConfig interface accepts the following options:

OptionTypeDefaultDescription
sdkKeystringrequiredSDK key from the Featureflip dashboard
baseUrlstringrequiredBase URL of the Evaluation API
streamingbooleantrueEnable SSE for real-time flag updates
pollIntervalnumber30000Polling interval in milliseconds (used when streaming is disabled)
flushIntervalnumber30000Interval in milliseconds between event flush batches
flushBatchSizenumber100Maximum number of events per flush batch
initTimeoutnumber10000Maximum time in milliseconds to wait for initialization
maxStreamRetriesnumber5Maximum SSE reconnection attempts before falling back to polling
const config: FeatureflipConfig = {
sdkKey: 'sdk-dev-abc123',
baseUrl: 'https://eval.featureflip.io',
streaming: true,
pollInterval: 60_000,
flushInterval: 15_000,
flushBatchSize: 50,
initTimeout: 5_000,
maxStreamRetries: 3,
};

Obtain a client via the static factory FeatureflipClient.get. There is no public constructor — the factory dedupes by SDK key, so every call with the same key returns a handle pointing at one shared underlying client (see Lifetime).

import { FeatureflipClient, createNodePlatform } from '@featureflip/js';
const client = FeatureflipClient.get(
{
sdkKey: 'sdk-dev-abc123',
baseUrl: 'https://eval.featureflip.io',
},
createNodePlatform(),
);
// Wait for flags to load before evaluating
await client.waitForInitialization();

The JS SDK is singleton-by-construction. FeatureflipClient.get(config, platform) is the only way to obtain a client, and the factory caches one shared core per SDK key:

  • First get() for a given SDK key constructs the shared core (platform, HTTP client, SSE connection, event processor), kicks off initialization, and returns a handle with refcount 1.
  • Subsequent get() calls with the same SDK key return a new handle and increment the refcount. The config and platform arguments are ignored on repeat calls; if the config differs meaningfully, a warning is logged and the cached instance is reused.
  • handle.close() decrements the refcount. When the refcount reaches zero, the shared core runs the real shutdown: closes the SSE connection, flushes pending events, clears timers, and removes itself from the factory cache.
  • Double-closing the same handle is a no-op.
  • Different SDK keys construct independent shared cores — multi-tenant use is fine.

This makes the SDK safe under any lifetime pattern, including per-request handler construction and scoped/transient DI registration — the extra get() calls allocate a cheap handle, not a new HTTP client or SSE stream. For tests that need a standalone instance not registered in the cache, use FeatureflipClient.forTesting(flags).

await client.waitForInitialization(): Promise<void>

Blocks until the client has fetched and stored the initial flag configuration. Rejects with an error if the fetch does not complete within initTimeout milliseconds.

client.isInitialized: boolean

A read-only property that returns true once the client has successfully loaded flag data.

All evaluation methods accept a flag key, an evaluation context, and a default value. If the flag is not found or an error occurs, the default value is returned.

const enabled = client.boolVariation('dark-mode', { user_id: 'u-123' }, false);

Signature:

boolVariation(key: string, context: EvaluationContext, defaultValue: boolean): boolean
const plan = client.stringVariation('pricing-tier', { user_id: 'u-123' }, 'free');

Signature:

stringVariation(key: string, context: EvaluationContext, defaultValue: string): string
const limit = client.numberVariation('rate-limit', { user_id: 'u-123' }, 100);

Signature:

numberVariation(key: string, context: EvaluationContext, defaultValue: number): number
interface BannerConfig {
text: string;
color: string;
}
const banner = client.jsonVariation<BannerConfig>(
'banner-config',
{ user_id: 'u-123' },
{ text: 'Welcome', color: '#000' },
);

Signature:

jsonVariation<T>(key: string, context: EvaluationContext, defaultValue: T): T

Use variationDetail() when you need to understand why a particular value was returned.

const detail = client.variationDetail('dark-mode', { user_id: 'u-123' }, false);
console.log(detail.value); // true
console.log(detail.reason); // 'RuleMatch'
console.log(detail.ruleId); // 'rule-abc'

Signature:

variationDetail<T>(key: string, context: EvaluationContext, defaultValue: T): EvaluationDetail<T>

Returns an EvaluationDetail<T> object:

PropertyTypeDescription
valueTThe evaluated flag value
reasonEvaluationReasonWhy this value was returned
variationKeystring | undefinedThe key of the matched variation
ruleIdstring | undefinedThe ID of the matched targeting rule
ValueDescription
'RuleMatch'A targeting rule matched the context
'Fallthrough'No rules matched; the fallthrough variation was served
'FlagDisabled'The flag is disabled; the off variation was served
'FlagNotFound'The flag key does not exist
'Error'An error occurred during evaluation

Records a custom analytics event.

client.track('purchase-completed', { user_id: 'u-123' }, {
plan: 'pro',
amount: 29.99,
});

Signature:

track(eventKey: string, context: EvaluationContext, metadata?: Record<string, unknown>): void

Events are batched and flushed automatically based on flushInterval and flushBatchSize.

Shuts down the client, flushes any pending events, stops SSE streaming or polling, and releases resources.

await client.close();

Signature:

close(): Promise<void>

Always call close() when the client is no longer needed to ensure events are delivered.

Creates a test client with hardcoded flag values. No network calls are made and no background processes are started.

const client = FeatureflipClient.forTesting({
'dark-mode': true,
'pricing-tier': 'enterprise',
'rate-limit': 500,
});
// Evaluations return the test values
client.boolVariation('dark-mode', {}, false); // true
client.stringVariation('pricing-tier', {}, 'free'); // 'enterprise'

Signature:

static forTesting(flags: Record<string, unknown>): FeatureflipClient

The test client is fully initialized immediately and returns the provided values regardless of the evaluation context.

type EvaluationContext = Record<string, unknown>;

A plain object containing user attributes for flag evaluation. The user_id property is used for percentage rollouts and event tracking.

const context: EvaluationContext = {
user_id: 'u-123',
country: 'US',
plan: 'pro',
};
interface EvaluationDetail<T = unknown> {
value: T;
variationKey?: string;
reason: EvaluationReason;
ruleId?: string;
}
type FlagType = 'Boolean' | 'String' | 'Number' | 'Json';
interface FeatureflipConfig {
sdkKey: string;
baseUrl: string;
streaming?: boolean;
pollInterval?: number;
flushInterval?: number;
flushBatchSize?: number;
initTimeout?: number;
maxStreamRetries?: number;
}
FeatureNode.jsBrowser
SSE headersCustom Authorization header via eventsource librarySDK key passed as query parameter (browser EventSource does not support custom headers)
User-AgentSet to featureflip-js/1.0.0Not set (browser fetch forbids User-Agent)
MD5 hashingNode.js crypto moduleBuilt-in pure-JS implementation