> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flagsync.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js SDK

> Integrate FlagSync feature flags into Node.js applications.

## Overview

The `@flagsync/node-sdk` integrates into Node.js applications for server-side feature management and event tracking—ideal for backend services and APIs.

<CardGroup cols={2}>
  <Card title="npm" icon="npm" href="https://www.npmjs.com/package/@flagsync/node-sdk">
    Latest version available on npm
  </Card>

  <Card title="GitHub" icon="github" href="https://github.com/flagsync/node-sdk/">
    Explore the source code on GitHub
  </Card>
</CardGroup>

## Installation

Install the SDK with your preferred package manager:

<CodeGroup>
  ```bash npm theme={null}
  npm install @flagsync/node-sdk
  npm install -D @flagsync/cli
  ```

  ```bash yarn theme={null}
  yarn add @flagsync/node-sdk
  yarn add -D @flagsync/cli
  ```

  ```bash pnpm theme={null}
  pnpm add @flagsync/node-sdk
  pnpm add -D @flagsync/cli
  ```
</CodeGroup>

## Quickstart

A basic example of using the SDK in a Node.js application (e.g., an Express server):

```typescript theme={null}
import express, { Request, Response } from 'express';
import { FlagSyncFactory, FsUserContext } from '@flagsync/node-sdk';
import { getFlagSyncUserContext } from '@/lib/flagsync/user-context';

const app = express();
const port = 3000;

// Initialize the SDK
const factory = FlagSyncFactory({ sdkKey: 'your-sdk-key'});

// Get the client
const client = factory.client();

// Wait for SDK readiness
await client.waitForReady();

app.get('/', async (req: Request, res: Response) => {
  const ctx = getFlagSyncUserContext(req);

  // Evaluate a flag with user context
  const isEnabled: boolean = client.flag(ctx, 'feature-enabled', false);

  if (isEnabled) {
    res.send('Feature is ON');
  } else {
    res.send('Feature is OFF');
  }
});

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});
```

<Tip>
  See [User Context Identification](#user-context-identification) for details on the `getFlagSyncUserContext` object.
</Tip>

## Initialization

### Get Your SDK Key

Find your server-side SDK key in your [workspace settings](https://www.flagsync.com/dashboard/settings/organization/workspaces/). Keep this key private and secure.

### Initialize the SDK

Initialize the SDK with your SDK key:

```typescript theme={null}
import { FlagSyncFactory } from '@flagsync/node-sdk';

const factory = FlagSyncFactory({
  sdkKey: 'your-sdk-key'
});

const client = factory.client();
```

### Wait for Readiness

Use promises or events to ensure the SDK is ready before evaluating flags.

Always wait for SDK initialization before evaluating any flags. Initialization usually completes within 30–50ms, depending on flag count and ruleset complexity.

<Steps>
  <Step title="Promises">
    <CodeGroup>
      ```typescript async/await theme={null}
      await client.waitForReady();
      // SDK is ready
      ```

      ```typescript async/await (throws) theme={null}
      // Throw an error if the SDK fails to initialize
      try {
        await client.waitForReadyCanThrow();
        // SDK is ready
      } catch (error) {
        // Handle initialization error
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Events">
    ```typescript theme={null}
    import { FsEvent } from '@flagsync/node-sdk';

    client.once(FsEvent.SDK_READY, () => {
      // SDK is ready
    });
    ```
  </Step>
</Steps>

#### SDK Not Ready

If the SDK isn't ready, it returns the `defaultValue` or `control`:

```typescript theme={null}
// SDK not ready
client.flag(ctx, 'flag-one', false); // false (defaultValue)
client.flag(ctx, 'flag-two');        // "control"
```

#### SDK Ready

Once ready, `flag()` returns the server-evaluated value:

```typescript theme={null}
await client.waitForReady();
const value = client.flag(ctx, 'flag-one'); // Server-evaluated value
```

## User Context Identification

Define the user context with a helper function that returns an `FsUserContext` object. Pass this object to `flag()` and `track()` functions.

<Note>
  User contexts enable personalized flag evaluations via [Individual Targeting](/flags/targeting-and-rollouts#user-segments) and consistent experiences during [Percentage Rollouts](/flags/targeting-and-rollouts#percentage-rollouts).
</Note>

<Tip>
  Ensure the `key` in `FsUserContext` is unique and persistent for accurate MAU tracking and consistent flag evaluations. See [User Context Best Practices](/sdks/overview#user-context-best-practices) for details.
</Tip>

<Steps>
  <Step title="Create a Helper Function">
    Create a helper function to construct the user context from request cookies, headers, or your application's auth system.

    Adjust the file below to meet your needs.

    ```typescript lib/flagsync/user-context.ts theme={null}
    export function getFlagSyncUserContext(req): FsUserContext {
      const userId = req.cookies['user-id'];
      const visitorId = req.cookies['visitor-id'];

      return {
        key: userId ?? visitorId,
        attributes: {
          userAgent: req.headers['user-agent'],
          region: req.headers['x-region'],
        }
      };
    }
    ```

    <Note>
      The `key` is set using a persistent `userId` or `visitorId` from cookies, falling back to a generated ID with `nanoid()`. For proper MAU tracking and consistent flag evaluations, ensure this `key` is unique and persistent across requests—see [User Context Best Practices](/sdks/overview#user-context-best-practices).
    </Note>
  </Step>

  <Step title="Set Cookies in Middleware (Optional)">
    Use Express middleware to set user identification cookies, simplifying context retrieval in the helper function.

    <Tip>
      Middleware is optional—user identification logic can be implemented directly in `getFlagSyncUserContext()` or elsewhere in your application.
    </Tip>

    ```typescript lib/flagsync/identify.ts theme={null}
    import { nanoid } from 'nanoid';
    import { verify } from 'jsonwebtoken';

    export function identifyUser(req, res, next) {
      // Replace this with your own logic to identify the user
      const user = verify(req.cookies['jwt'], process.env.JWT_SECRET)

      if (user?.userId) {
        // Set the user-id cookie
        res.cookie('user-id', user.userId);
      } else {
        // Set the visitor-id cookie
        const visitorId = req.cookies['visitor-id'] ?? nanoid();
        res.cookie('visitor-id', visitorId);
      }

      next();
    }
    ```
  </Step>

  <Step title="Use the Helper in Routes">
    In your Express routes, use the helper to build the `FsUserContext` for flag evaluations or event tracking.

    ```typescript app.ts {11-12} theme={null}
    import express from 'express';
    import cookieParser from 'cookie-parser';
    import { identifyUser } from '@/lib/flagsync/identify';
    import { getFlagSyncUserContext } from '@/lib/flagsync/user-context';

    const app = express();
    app.use(cookieParser());
    app.use(identifyUser);  // Identify user middleware

    app.post('/feature', async (req, res) => {
      const ctx = getFlagSyncUserContext(req);
      const isEnabled = client.flag(ctx, 'feature-enabled', false);
      res.status(200).json({ enabled: isEnabled });
    });
    ```
  </Step>
</Steps>

## Usage

### Evaluate Flags

Evaluates a feature flag for a given user context. Applies targeting rules, rollouts, and defaults values.

```typescript theme={null}
client.flag<T>(ctx: FsUserContext, flagKey: string, defaultValue?: T): T
```

```typescript app.ts {3} theme={null}
app.post('/feature', async (req, res) => {
  const ctx = getFlagSyncUserContext(req);
  const isEnabled = client.flag(ctx, 'feature-enabled', false);
  res.status(200).json({ enabled: isEnabled });
});
```

#### FlagSync CLI

When using the CLI, flag values are automatically inferred from the generated types:

```typescript theme={null}
client.flag(ctx, 'layout');              // Type: 'v1' | 'v2' | 'v3'
client.flag(ctx, 'killswitch');          // Type: boolean
client.flag(ctx, 'price-discount');      // Type: 0.1 | 0.2
client.flag(ctx, 'price-discount', 0.5); // ❌ TS Error: Invalid default value
client.flag(ctx, 'invalid-flag');        // ❌ TS Error: Invalid flag key
```

Without generated types, you **must** manually specify the type:

```typescript theme={null}
client.flag<'v1' | 'v2'>(ctx, 'layout');            // Type: 'v1' | 'v2'
client.flag<number>(ctx, 'price-discount');         // Type: number
client.flag<boolean>(ctx, 'enable-feature', false); // Type: boolean (defaultValue must be true or false)
client.flag(ctx, 'no-type')                         // Type: unknown
```

<Tip>
  An [Impression](/impressions/generating) is automatically registered when `flag()` is called.
</Tip>

### Track Events

Submit user actions with the `track()` function:

<CodeGroup>
  ```typescript app.ts {8, 15} theme={null}
  app.post('/checkout', async (req: Request, res: Response) => {
    const request: PurchaseRequest = req.body;
    const ctx: FsUserContext = {
      key: 'user-123',
      email: 'user@example.com',
    };

    client.track(ctx, 'purchase-request', null, request);

    const t0 = Date.now();
    const order = await makePurchase(request);
    const receipt = await sendOrder(order);
    const t1 = Date.now();

    client.track(ctx, 'purchase-duration', t1 - t0);

    res.status(200).json({ success: true, receipt });
  });
  ```

  ```typescript track() signature theme={null}
  track(
    context: FsUserContext,
    eventKey: string,
    eventValue?: number | null,
    properties?: Record<string, any> | null
  );
  ```
</CodeGroup>

<Tip>
  See [Events: Tracking](/events/tracking) to learn about numeric and property events.
</Tip>

### SDK Event Listeners

The SDK emits these events for SDK lifecycle management:

* `SDK_UPDATE`: Emitted when flags are updated
* `SDK_READY`: Emitted when the SDK is ready
* `ERROR`: Emitted when an error occurs during initialization

```typescript theme={null}
import { FsEvent } from '@flagsync/node-sdk';

// Flag updates
client.on(FsEvent.SDK_UPDATE, () => {
  console.log(`Flags updated at ${new Date().toISOString()}`);
});
```

<Note>
  `SDK_UPDATE` does not fire if syncing is disabled.
</Note>

## Error Handling

```typescript theme={null}
import { FsServiceError } from '@flagsync/node-sdk';

// Via events
client.on(FsEvent.ERROR, (error: FsServiceError) => {
  console.error('SDK Error:', error);
});

// Via promises
try {
  await client.waitForReadyCanThrow();
} catch (error) {
  console.error('Initialization Error:', error as FsServiceError);
}
```

<Tip>
  All exposed errors are normalized to `FsServiceError`.
</Tip>

## Configuration

Configure the SDK with the `FsConfig` interface:

```typescript theme={null}
export interface FsConfig {
  sdkKey: string;
  sync?: {
    type?: 'ws' | 'sse' | 'poll' | 'off'; // Optional: Sync strategy (default: 'ws')
    pollRateInSec?: number;               // Optional: Polling interval in seconds
  };
  tracking?: {
    impressions?: {
      maxQueueSize: number;           // Required: Max impressions queue size
      pushRateInSec: number;          // Required: Impressions push rate (seconds)
    };
    events?: {
      maxQueueSize: number;           // Required: Max events queue size
      pushRateInSec: number;          // Required: Events push rate (seconds)
    };
  };
  urls?: {
    ws?: string;                      // Optional: WebSocket sync URL
    sse?: string;                     // Optional: SSE sync URL
    flags?: string;                   // Optional: Flag fetch URL
    events?: string;                  // Optional: Events & impressions URL
  };
  logger?: Partial<ILogger>;          // Optional: Custom logger
  logLevel?: LogLevel;                // Optional: Logging level
  metadata?: Record<string, any>;     // Optional: Additional metadata
}
```

### Custom Attributes

* Define user attributes for targeting in [Flags: User Segments](/flags/targeting-and-rollouts#user-segments).
* See [User Context Identification](#user-context-identification) for the recommended approach to building the context.

```typescript theme={null}
const ctx: FsUserContext = {
  key: 'user-123',
  attributes: {
    plan: 'premium',
    country: 'US',
    userType: 'enterprise'
  }
}
```

<Warning>
  Ensure the `key` in `FsUserContext` is unique and persistent for accurate MAU tracking and consistent flag evaluations. See [User Context Best Practices](/sdks/overview#user-context-best-practices) for details.
</Warning>

### Flag Syncing

Configure flag update strategies with the `sync` object: `ws`, `sse`, `poll`, or `off`.

<Tip>
  By default, flag updates propagate in milliseconds over a WebSocket connection, ensuring the latest values are used in evaluations.
</Tip>

<Steps>
  <Step title="WebSocket (Default)">
    Stream updates over a WebSocket connection—flag updates are reevaluated on the server and pushed to the client in real time:

    ```typescript theme={null}
    const factory = FlagSyncFactory({
      sdkKey: 'your-sdk-key',
      sync: {
        type: 'ws' // Default
      }
    });
    ```
  </Step>

  <Step title="SSE">
    Stream updates via server-sent events (SSE):

    ```typescript theme={null}
    const factory = FlagSyncFactory({
      sdkKey: 'your-sdk-key',
      sync: {
        type: 'sse'
      }
    });
    ```
  </Step>

  <Step title="Polling">
    Poll the server on an interval:

    ```typescript theme={null}
    const factory = FlagSyncFactory({
      sdkKey: 'your-sdk-key',
      sync: {
        type: 'poll',
        pollRateInSec: 60
      }
    });
    ```
  </Step>

  <Step title="Off">
    Disable syncing:

    ```typescript theme={null}
    const factory = FlagSyncFactory({
      sdkKey: 'your-sdk-key',
      sync: {
        type: 'off'
      }
    });
    ```
  </Step>
</Steps>

## Best Practices

* Wait for SDK readiness before evaluating flags.
* Select a sync strategy (`ws`/`sse`/`poll`/`off`) based on your application's needs.
* Follow [User Context Identification](#user-context-identification) for simplified context management.
* Add user attributes for targeted feature rollouts.

## Environment Variables

Set the following environment variable:

* `FLAGSYNC_SDK_KEY`: Your server-side FlagSync SDK key (required)
