Evaluate feature flags in your server-side Node.js application using the WorkOS runtime client.
Use the quick start guide when you want to gate authenticated application behavior based on the user’s session. This is a good fit when feature flag updates can take effect the next time the user’s session is refreshed.
Use the runtime client when you need server-side evaluation that stays in sync independently of user authentication, such as in backend services, jobs, webhooks, or other long-lived server processes.
The WorkOS Node SDK includes a runtime client for evaluating feature flags in server-side applications. The client maintains internal flag state, allowing it to serve feature flags without remote requests. Flag configurations stay in sync with the dashboard automatically.
Before the first successful sync, evaluations use bootstrap data if provided. Otherwise, they fall back to the flag’s default_value, or false for unknown flags.
Create a single, shared runtime client instance when your application starts. Do not create a new client per request – the client handles synchronization and evaluation for the lifetime of the process.
Install the WorkOS Node SDK using your preferred package manager.
npm install @workos-inc/node
To make calls to WorkOS, provide your API key. Store it as a managed secret, such as WORKOS_API_KEY, and pass it to the SDK through your application’s environment.
WORKOS_API_KEY='sk_example_123456789'
Initialize the WorkOS client, create a runtime client, and evaluate flags once the client is ready.
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient(); try { await client.waitUntilReady({ timeoutMs: 5000 }); const isEnabled = client.isEnabled('advanced-analytics', { organizationId: 'org_01EHQMYV6MBK39QC5PZXHY59C3', }); console.log('Feature enabled:', isEnabled); } catch (error) { console.error('Runtime client failed to initialize:', error); }
The isEnabled method returns true or false based on the flag’s configuration and any targeting rules that match the provided context.
You can also listen for the ready event instead of using waitUntilReady():
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient(); client.on('ready', () => { const isEnabled = client.isEnabled('advanced-analytics'); console.log('Feature enabled:', isEnabled); });
Pass an organizationId, userId, or both to evaluate flags against the targeting rules configured in the dashboard.
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient(); client.on('ready', () => { // Evaluate with organization targeting const isEnabled = client.isEnabled('advanced-analytics', { organizationId: 'org_01EHQMYV6MBK39QC5PZXHY59C3', }); // Evaluate with user targeting const isEnabledForUser = client.isEnabled('advanced-analytics', { userId: 'user_01E4ZCR3C56J083X43JQXF3JK5', }); // Evaluate with both organization and user targeting const isEnabledForBoth = client.isEnabled('advanced-analytics', { organizationId: 'org_01EHQMYV6MBK39QC5PZXHY59C3', userId: 'user_01E4ZCR3C56J083X43JQXF3JK5', }); });
If no context is provided, isEnabled returns the flag’s default_value. If the flag does not exist, it returns false – you can override this by passing a third argument as the default return value.
When both userId and organizationId are provided, user targeting takes precedence over organization targeting.
Call close() to stop background synchronization and clean up resources when your server shuts down.
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient(); // Gracefully shut down on server termination process.on('SIGTERM', async () => { await client.close(); process.exit(0); }); process.on('SIGINT', async () => { await client.close(); process.exit(0); });
The runtime client emits events to signal state changes during its lifecycle.
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient(); // Fires once when flag data is first available client.on('ready', () => { console.log('Runtime client is ready'); }); // Fires when a flag's configuration changes after initialization client.on('change', ({ key, previous, current }) => { console.log(`Flag "${key}" changed`, { previous, current }); }); // Fires when a polling request fails client.on('error', (error) => { console.error('Polling error:', error.message); }); // Fires on 401 Unauthorized — polling stops automatically client.on('failed', (error) => { console.error('Client failed:', error.message); });
| Event | Description |
|---|---|
ready | Fires once when flag data is first available, either from bootstrap or the first successful sync. |
change | Fires when a flag’s configuration changes after initialization. Receives { key, previous, current }. |
error | Fires when a sync request fails. The client retries automatically with exponential backoff. |
failed | Fires on 401 Unauthorized. The client stops syncing automatically. |
failed, your API key is invalid or unauthorized and synchronization stops until the client is recreated with valid credentials.error, a sync request failed temporarily. The client continues retrying automatically in the background.Pre-populate the client with flag data so evaluations are available immediately, before the first sync completes.
import { WorkOS } from '@workos-inc/node'; const workos = new WorkOS(process.env.WORKOS_API_KEY); const client = workos.featureFlags.createRuntimeClient({ bootstrapFlags: { 'advanced-analytics': { slug: 'advanced-analytics', enabled: true, default_value: false, targets: { users: [], organizations: [] }, }, }, }); // Flags are available immediately from the bootstrap data const isEnabled = client.isEnabled('advanced-analytics'); console.log('Feature enabled:', isEnabled); // The client will replace bootstrap data with live data // once the first poll completes
Bootstrap data is replaced with live data after the first successful sync.
Pass options to createRuntimeClient() to customize client behavior.
| Option | Type | Default | Description |
|---|---|---|---|
pollingIntervalMs | number | 30000 | How often to sync flag changes, in milliseconds. Minimum 5000 |
requestTimeoutMs | number | 10000 | Timeout for each sync request, in milliseconds |
bootstrapFlags | Record<string, FlagPollEntry> | undefined | Pre-populated flag data for instant evaluation before the first sync |
logger | RuntimeClientLogger | undefined | Custom logger with debug, info, warn, and error methods |
If you’re using TypeScript, the @workos-inc/node package includes built-in type definitions. To learn more about the specific configuration options and types available for the runtime client, read the @workos-inc/node package docs. Key exports include RuntimeClientOptions, EvaluationContext, FeatureFlagsRuntimeClient, and RuntimeClientStats.