SDK Integration
Evaluate feature flags in your server-side Node.js application using the WorkOS runtime client.
There are two ways to read feature flags in your application. Which ones are open to you depends on the platform, because the runtime client ships in the Node SDK only. If your backend runs on Node, use the runtime client.
| Platform | Approach | Flag changes take effect |
|---|---|---|
| Node | Runtime client | Within the polling interval |
| Any other platform | feature_flags access token claim |
Next time the session refreshes |
Every flag in the feature_flags claim takes up space in the access token, which is stored in the AuthKit session cookie. Browsers cap how large a cookie can be, which is why JWT templates must render to 3072 bytes or smaller. A growing flag set eats into that budget, and so do role and permission claims. The runtime client evaluates flags server-side without putting them in the token, so the number of flags stops being a constraint.
Use the runtime client for server-side evaluation that stays in sync independently of user authentication, such as in backend services, jobs, webhooks, or other long-lived server processes. It keeps every active flag out of the user’s access token and session cookie, which makes it the right default for an application whose flag set grows over time.
The rest of this page covers the runtime client, and applies to Node only.
Read the claim to gate authenticated application behavior from the user’s session, when flag updates can wait until the session refreshes. On platforms without a runtime client this is how you use feature flags. On Node it still works well for a small, stable set of flags.
The claim travels inside the access token, so this approach works on any platform whose AuthKit SDK gives you an authenticated session. In @workos-inc/authkit-nextjs, read it from the current session with withAuth(). In the server-side SDKs, load the sealed session, authenticate it, then read feature_flags from the access token. The quick start guide walks through it.
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.
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.
Initialize the WorkOS client, create a runtime client, and evaluate flags once the client is ready.
The isEnabled method returns true or false based on the flag’s configuration and any targeting rules that match the provided context.
If you’re using @workos-inc/authkit-nextjs, use getFeatureFlagsRuntimeClient() instead of creating the runtime client directly. The helper returns one shared runtime client for the server process, so repeated calls across routes or server components do not create extra polling clients.
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. |
- If all flags return their default values, the client may not be ready yet, the flag key may be incorrect, or no targeting rules may match the evaluation context.
- If the client emits
failed, your API key is invalid or unauthorized and synchronization stops until the client is recreated with valid credentials. - If the client emits
error, a sync request failed temporarily. The client continues retrying automatically in the background. - If dashboard changes do not appear immediately, remember that the runtime client refreshes on the configured polling interval.
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.