JavaScript / TypeScript SDK Reference
TL;DR: Install
@featureflip/sdk, callclient.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.
Requirements
Section titled “Requirements”- Node.js 20.19.0+ (for Node.js usage) or a modern browser
- TypeScript 5.0+ (optional, for type definitions)
Installation
Section titled “Installation”npm install @featureflip/sdkThe 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';Configuration
Section titled “Configuration”The FeatureflipConfig interface accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
sdkKey | string | required | SDK key from the Featureflip dashboard |
baseUrl | string | required | Base URL of the Evaluation API |
streaming | boolean | true | Enable SSE for real-time flag updates |
pollInterval | number | 30000 | Polling interval in milliseconds (used when streaming is disabled) |
flushInterval | number | 30000 | Interval in milliseconds between event flush batches |
flushBatchSize | number | 100 | Maximum number of events per flush batch |
initTimeout | number | 10000 | Maximum time in milliseconds to wait for initialization |
maxStreamRetries | number | 5 | Maximum SSE reconnection attempts before falling back to polling |
Example configuration
Section titled “Example configuration”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,};Initialization
Section titled “Initialization”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 evaluatingawait client.waitForInitialization();Lifetime
Section titled “Lifetime”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. Theconfigandplatformarguments 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).
waitForInitialization()
Section titled “waitForInitialization()”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.
isInitialized
Section titled “isInitialized”client.isInitialized: booleanA read-only property that returns true once the client has successfully loaded flag data.
Evaluating Flags
Section titled “Evaluating Flags”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.
boolVariation()
Section titled “boolVariation()”const enabled = client.boolVariation('dark-mode', { user_id: 'u-123' }, false);Signature:
boolVariation(key: string, context: EvaluationContext, defaultValue: boolean): booleanstringVariation()
Section titled “stringVariation()”const plan = client.stringVariation('pricing-tier', { user_id: 'u-123' }, 'free');Signature:
stringVariation(key: string, context: EvaluationContext, defaultValue: string): stringnumberVariation()
Section titled “numberVariation()”const limit = client.numberVariation('rate-limit', { user_id: 'u-123' }, 100);Signature:
numberVariation(key: string, context: EvaluationContext, defaultValue: number): numberjsonVariation()
Section titled “jsonVariation()”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): TEvaluation Details
Section titled “Evaluation Details”Use variationDetail() when you need to understand why a particular value was returned.
variationDetail()
Section titled “variationDetail()”const detail = client.variationDetail('dark-mode', { user_id: 'u-123' }, false);
console.log(detail.value); // trueconsole.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:
| Property | Type | Description |
|---|---|---|
value | T | The evaluated flag value |
reason | EvaluationReason | Why this value was returned |
variationKey | string | undefined | The key of the matched variation |
ruleId | string | undefined | The ID of the matched targeting rule |
EvaluationReason
Section titled “EvaluationReason”| Value | Description |
|---|---|
'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 |
Event Tracking
Section titled “Event Tracking”track()
Section titled “track()”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>): voidEvents are batched and flushed automatically based on flushInterval and flushBatchSize.
Cleanup
Section titled “Cleanup”close()
Section titled “close()”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.
Testing
Section titled “Testing”FeatureflipClient.forTesting()
Section titled “FeatureflipClient.forTesting()”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 valuesclient.boolVariation('dark-mode', {}, false); // trueclient.stringVariation('pricing-tier', {}, 'free'); // 'enterprise'Signature:
static forTesting(flags: Record<string, unknown>): FeatureflipClientThe test client is fully initialized immediately and returns the provided values regardless of the evaluation context.
EvaluationContext
Section titled “EvaluationContext”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',};EvaluationDetail<T>
Section titled “EvaluationDetail<T>”interface EvaluationDetail<T = unknown> { value: T; variationKey?: string; reason: EvaluationReason; ruleId?: string;}FlagType
Section titled “FlagType”type FlagType = 'Boolean' | 'String' | 'Number' | 'Json';FeatureflipConfig
Section titled “FeatureflipConfig”interface FeatureflipConfig { sdkKey: string; baseUrl: string; streaming?: boolean; pollInterval?: number; flushInterval?: number; flushBatchSize?: number; initTimeout?: number; maxStreamRetries?: number;}Platform Differences
Section titled “Platform Differences”| Feature | Node.js | Browser |
|---|---|---|
| SSE headers | Custom Authorization header via eventsource library | SDK key passed as query parameter (browser EventSource does not support custom headers) |
User-Agent | Set to featureflip-js/1.0.0 | Not set (browser fetch forbids User-Agent) |
| MD5 hashing | Node.js crypto module | Built-in pure-JS implementation |
See also
Section titled “See also”- JavaScript Quickstart — get started in under 5 minutes
- Node.js SDK — Node.js-specific wrapper with simpler setup
- Targeting & Segments — control which users see which variation
- Rollout Strategies — gradual percentage-based rollouts
- Evaluation API — REST API reference
- SDK Overview — compare server-side and client-side SDKs