Client SDKs
The EdgeFlags client SDKs provide a type-safe, real-time interface for reading feature flags and configs. The core SDK (@edgeflags/sdk) works in any JavaScript runtime, and the React package (@edgeflags/react) adds hooks and a context provider.
Both packages use the bulk evaluation endpoint under the hood — flags and configs are fetched in a single request and cached locally for synchronous reads.
Installation
npm install @edgeflags/sdk# React bindings (optional)npm install @edgeflags/reactpnpm add @edgeflags/sdk# React bindings (optional)pnpm add @edgeflags/reactyarn add @edgeflags/sdk# React bindings (optional)yarn add @edgeflags/reactQuick start
import { EdgeFlags } from '@edgeflags/sdk';
const ef = new EdgeFlags({ token: 'ff_production_abc123', baseUrl: 'https://edgeflags.net', context: { user_id: 'u_42', plan: 'premium', custom: { country: 'US' }, },});
await ef.init();
const darkMode = ef.flag('dark_mode', false);const limits = ef.config('api_limits', { requests_per_minute: 100 });After init() resolves, all reads are synchronous from the local cache. The SDK polls for updates in the background.
Configuration
Pass these options to the EdgeFlags constructor:
| Option | Type | Default | Description |
|---|---|---|---|
token | string | required | Bearer token for authentication |
baseUrl | string | required | EdgeFlags API URL |
context | EvaluationContext | undefined | User context for targeting evaluation |
pollingInterval | number | 60000 | Background poll interval in milliseconds |
bootstrap | object | undefined | Initial flag/config values for instant reads before init() |
debug | boolean | false | Log SDK activity to the console |
Evaluation context
The context object is sent with every evaluation request and drives targeting rules:
interface EvaluationContext { user_id?: string; email?: string; phone?: string; plan?: string; segments?: string[]; environment?: string; custom: Record<string, unknown>;}Reading flags
Use flag() to read a flag value. Pass a default as the second argument — its type narrows the return type.
// Returns boolean | string | number | object | undefinedconst raw = ef.flag('dark_mode');
// Returns boolean (default provides type + fallback)const darkMode = ef.flag('dark_mode', false);
// String flagsconst banner = ef.flag('banner_text', '');
// Number flagsconst limit = ef.flag('request_limit', 1000);
// JSON flagsconst theme = ef.flag('theme_override', { primary: '#000' });Use allFlags() to get every cached flag as a Record<string, FlagValue>.
Reading configs
Use config() the same way. The generic parameter provides type safety:
const providers = ef.config<PaymentConfig>('payment_providers', { stripe_enabled: true, paypal_enabled: false,});
const all = ef.allConfigs();Identifying users
Call identify() to update the evaluation context and immediately refresh all values:
await ef.identify({ user_id: 'u_99', plan: 'enterprise', custom: { beta_enrolled: true },});
// Flags and configs now reflect the new userEvents
The SDK emits three events:
| Event | Payload | When |
|---|---|---|
ready | undefined | init() completes successfully |
change | ChangeEvent | Flag or config values change after a poll |
error | Error | Network or API error during polling |
// Subscribe — returns an unsubscribe functionconst off = ef.on('change', (event) => { for (const { key, previous, current } of event.flags) { console.log(`Flag ${key}: ${previous} → ${current}`); }});
ef.on('error', (err) => { console.error('EdgeFlags polling error:', err);});
// Unsubscribeoff();Bootstrap and offline fallback
Provide bootstrap values so flags are available immediately, before the first network request:
const ef = new EdgeFlags({ token: 'ff_production_abc123', baseUrl: 'https://edgeflags.net', bootstrap: { flags: { dark_mode: false, new_checkout: true }, configs: { api_limits: { requests_per_minute: 500 } }, },});
// Available immediately — no await neededconst darkMode = ef.flag('dark_mode', false);
// init() will replace bootstrap values with live dataawait ef.init();React
The @edgeflags/react package provides a context provider and hooks that trigger re-renders when values change.
Provider setup
Wrap your app with EdgeFlagsProvider. You can either pass configuration props or an existing client instance:
import { EdgeFlagsProvider } from '@edgeflags/react';
function App() { return ( <EdgeFlagsProvider token="ff_production_abc123" baseUrl="https://edgeflags.net" context={{ user_id: currentUser.id, plan: currentUser.plan, custom: {}, }} > <YourApp /> </EdgeFlagsProvider> );}Or with an external client (useful when you need access to the client outside React):
import { EdgeFlags } from '@edgeflags/sdk';import { EdgeFlagsProvider } from '@edgeflags/react';
const client = new EdgeFlags({ token, baseUrl, context });await client.init();
function App() { return ( <EdgeFlagsProvider client={client}> <YourApp /> </EdgeFlagsProvider> );}useFlag
Read a flag value. The component re-renders when the value changes.
import { useFlag } from '@edgeflags/react';
function Checkout() { const newCheckout = useFlag('new_checkout', false);
return newCheckout ? <NewCheckoutFlow /> : <LegacyCheckout />;}useConfig
Read a config value with the same re-render behavior.
import { useConfig } from '@edgeflags/react';
function PaymentForm() { const config = useConfig<PaymentConfig>('payment_providers', { stripe_enabled: true, paypal_enabled: false, });
return config.stripe_enabled ? <StripeForm /> : null;}useEdgeFlags
Access the underlying EdgeFlags client for manual operations like identify() or refresh().
import { useEdgeFlags } from '@edgeflags/react';
function LoginHandler() { const ef = useEdgeFlags();
async function onLogin(user: User) { await ef.identify({ user_id: user.id, email: user.email, plan: user.plan, custom: {}, }); }
return <LoginForm onSuccess={onLogin} />;}Testing
Use createMockClient() to create a client that returns static values without making network requests:
import { createMockClient } from '@edgeflags/sdk';
const mock = createMockClient({ flags: { dark_mode: true, new_checkout: false }, configs: { api_limits: { requests_per_minute: 9999 } },});
// Works like a real clientmock.flag('dark_mode', false); // truemock.config('api_limits'); // { requests_per_minute: 9999 }In React tests, pass the mock client to the provider:
import { createMockClient } from '@edgeflags/sdk';import { EdgeFlagsProvider } from '@edgeflags/react';
const mock = createMockClient({ flags: { dark_mode: true } });
render( <EdgeFlagsProvider client={mock}> <ComponentUnderTest /> </EdgeFlagsProvider>);Cleanup
Call destroy() to stop polling and release resources:
ef.destroy();In React, the provider handles cleanup automatically when it unmounts. If you passed an external client, you are responsible for calling destroy() yourself.