> ## 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.

# Next.js SDK

> Integrate FlagSync feature flags into Next.js applications with Vercel Flags.

## Overview

The `@flagsync/nextjs-sdk` integrates into Next.js applications with [Vercel Flags](https://flags-sdk.dev/) for feature management and event tracking—ideal for SSR and static site generation workflows.

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

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

## Installation

Install the SDK and required dependencies with your preferred package manager:

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

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

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

## Quickstart

Integrate FlagSync with [Vercel Flags](https://flags-sdk.dev/) in your Next.js app in just a few steps:

<Steps>
  <Step title="Initialize the Client">
    Create the `client` and export a boolean `adapter` helper:

    ```ts @/lib/flagsync theme={null}
    import { createAdapter, createClient } from '@flagsync/nextjs-sdk';

    const client = createClient({
      sdkKey: process.env.FLAGSYNC_SDK_KEY!,
    });

    // Boolean adapter
    export const adapter = createAdapter(client)<boolean>();
    ```
  </Step>

  <Step title="Define Your Feature Flag">
    Use the `flag()` function to define your feature flag, and connect it to the `adapter`.

    ```ts @/lib/flagsync/flags theme={null}
    import { flag } from 'flags/next';
    import { adapter } from '@/lib/flagsync';

    export const killswitchFlag = flag({
      key: 'my-first-kill-switch',
      adapter,
      identify: () => ({ key: 'user-123' }), // See note below
    });
    ```

    <Tip>
      See [User Context Identification](#user-context-identification) for the recommended approach to building the context.
    </Tip>
  </Step>

  <Step title="Use the Flag in a Server Component">
    Call your feature flag like an async function in a Server Component to get the evaluated value.

    ```tsx app/page.tsx theme={null}
    import { myFlag } from '@/lib/flagsync/flags';

    export default async function Page() {
      const value = await killswitchFlag();

      return <div>{killswitchFlag.key} is {value}</div>;
    }
    ```
  </Step>
</Steps>

## Initialization

### Get Your SDK Key

Find your server-side SDK key in your [workspace settings](https://www.flagsync.com/dashboard/settings/organization/workspaces/). Keep server-side keys private to protect flag rules.

### Initialize the SDK

Initialize the FlagSync client singleton with your server-side SDK key:

```typescript lib/flagsync.ts theme={null}
import { createClient } from '@flagsync/nextjs-sdk';

export const client = createClient({
  sdkKey: process.env.FLAGSYNC_SDK_KEY!,
});
```

### Wait for Readiness

The SDK automatically initializes during the Next.js App Router lifecycle—no manual waiting is required for Server Components.

## User Context Identification

Define the user context with a custom `identify` function that returns an `FsUserContext` object. Pass this function to Vercel’s `flag()` function.

<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 `identify` with the `createIdentify` helper, linking flags to user contexts.

    ```typescript @/lib/flagsync {14} theme={null}
    import {
      createAdapter,
      createClient,
      createIdentify,
    } from '@flagsync/nextjs-sdk';

    import { getFlagSyncUserContext } from '@/lib/flagsync/user-context';

    const client = createClient({
      sdkKey: process.env.FLAGSYNC_SDK_KEY!,
    });

    export const adapter = createAdapter(client)<boolean>();
    export const identify = createIdentify(getFlagSyncUserContext);
    ```
  </Step>

  <Step title="Set Up Identification">
    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.

    ```ts @/lib/flagsync/user-context {10} theme={null}
    import type { ReadonlyHeaders, ReadonlyRequestCookies } from 'flags';
    import type { NextRequest } from 'next/server';

    type FlagParamsType = {
      cookies: ReadonlyRequestCookies | NextRequest['cookies'],
      headers: ReadonlyHeaders | NextRequest['headers'],
    };

    // Return the user context (FsUserContext)
    export async function getFlagSyncUserContext(
      params: FlagParamsType
    ): FsUserContext {
      const userId = cookies.get('user-id')?.value;
      const visitorId = cookies.get('visitor-id')?.value;

      return {
        key: userId ?? visitorId,
        attributes: {
          region: params.headers.get('x-region'),
          userAgent: params.headers.get('user-agent')
        }
      };
    };
    ```

    <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 middleware to set user identification cookies, simplifying context retrieval in `getFlagSyncUserContext`.

    ```typescript middleware.ts theme={null}
    import { jwtVerify } from 'jose';
    import { NextRequest, NextResponse } from 'next/server';
    import { nanoid } from 'nanoid';

    const jwtSecret = new TextEncoder().encode('your-jwt-secret-key');

    export async function middleware(request: NextRequest) {
      const response = NextResponse.next();
      const jwt = request.cookies.get('jwt')?.value;

      // Replace this with your own logic to identify the user
      let user;
      if (jwt) {
        const { payload } = await jwtVerify(jwt, jwtSecret);
        user = payload;
      }

      if (user?.userId) {
        // Set the user-id cookie
        response.cookies.set('user-id', user.userId);
      } else {
        // Set the visitor-id cookie
        const visitorId = request.cookies.get('visitor-id')?.value
        response.cookies.set('visitor-id', visitorId ?? nanoid());
      }

      return response;
    }
    ```
  </Step>

  <Step title="Use the Helper in Flag Definitions">
    Pass `identify` to the flag definition. This connects flag evaluation to the user context.

    ```typescript @/lib/flagsync/flags {7} theme={null}
    import { flag } from 'flags/next';
    import { adapter, identify } from '@/lib/flagsync';

    export const killswitchFlag = flag({
      key: 'my-first-kill-switch',
      adapter,
      identify,
    });
    ```
  </Step>
</Steps>

## Usage

### Evaluate Flags

The are two distinct approaches for evaluating flags, each with different setup requirements and type-safety guarantees.

* **With the FlagSync CLI (Recommended)**: This approach provides maximum type safety. It automatically generates types that validate your flag keys and infer their specific return values.
* **Manual Typing**: If you aren't using the CLI, this method still provides strong type safety for a flag's return value, but requires you to create and manage type-specific adapters manually.

#### FlagSync CLI

When using the CLI, you must use the `createTypedFlag` function from the `@flagsync/nextjs-sdk` to build a type-safe flag function.

This approach provides full type safety: from key validation to inferred return values, which isn't possible with the native `flags/next` package as it's unaware of your custom types.

<Steps>
  <Step title="Generate Types">
    First, run the [FlagSync CLI](/sdks-cli/cli) to generate a `flags.d.ts` file, mapping your flag keys to their specific data types.

    ```typescript gen/flags.d.ts theme={null}
    // THIS FILE WAS GENERATED BY FLAGSYNC-CLI
    import { FeatureFlags } from "@flagsync/nextjs-sdk";

    declare module "@flagsync/nextjs-sdk" {
      export interface FeatureFlags {
        "my-first-kill-switch": boolean;
        "price-discount": 0.1 | 0.2 | 0.3;
      }
    }
    ```
  </Step>

  <Step title="Build the Type-Safe Flag Function">
    Create and export a `flag` function with the `createTypedFlag` factory.

    ```typescript @lib/flagsync {13} theme={null}
    import {
      createClient,
      createIdentify,
      createTypedFlag,
    } from '@flagsync/nextjs-sdk';

    import { getFlagSyncUserContext } from '@/lib/flagsync/user-context';

    const client = createClient({
      sdkKey: process.env.FLAGSYNC_SDK_KEY!
    });

    export const { flag } = createTypedFlag(client);
    export const identify = createIdentify(getFlagSyncUserContext);
    ```
  </Step>

  <Step title="Define Your Flags">
    Now you can define all your flags. Thanks to the generated types, you'll get full autocompletion for keys, and TypeScript will automatically infer the correct return type for each flag.

    ```typescript @lib/flagsync/flags {4,10} theme={null}
    import { flag, identify } from '@/lib/flagsync';

    // The return value of killswitchFlag() is now Promise<boolean>.
    export const killswitchFlag = flag({
      identify,
      key: 'my-first-kill-switch',
    });

    // The return value of discountFlag() is now Promise<0.1 | 0.2 | 0.3>
    export const discountFlag = flag({
      identify,
      key: 'price-discount',
    });
    ```

    <Check>
      There's no need for an `adapter` when using the typed flag function.
    </Check>
  </Step>

  <Step title="Use in a Server Component">
    Await the flag definition in Server Components:

    ```jsx app/page.tsx theme={null}
    import { killswitchFlag } from '@/lib/flagsync/flags';

    export default async function Page() {
      const value = await killswitchFlag(); // Promise<boolean>

      return (
        <div>The value of {killswitchFlag.key} is {value}</div>
      );
    }
    ```
  </Step>
</Steps>

<Tip>
  An [Impression](/impressions/generating) is automatically registered when you evaluate the flag.
</Tip>

#### Manual Typing

If you're not using the FlagSync CLI, you can still achieve type safety on your flag's return values. This approach requires you to manually specify each flag's expected type by creating and using type-specific adapters.

You'll use the native `flag` function from `flags/next` and provide it with an adapter that has been pre-typed.

<Steps title="Create Typed Adapters">
  <Step title="Use the AdapterFactory">
    Create an adapter for each data type you need. This ensures that the return value of your flag function is correctly typed.

    ```typescript @lib/flagsync {17-20} theme={null}
    import {
    createClient,
    createIdentify,
    createAdapter, // Renamed from createTypedFlag for clarity
    } from '@flagsync/nextjs-sdk';

    import { getContext } from '@/lib/flagsync/user-context';

    export const client = createClient({
    sdkKey: process.env.FLAGSYNC_SDK_KEY!,
    });

    // The factory creates adapters
    const adapterFactory = createAdapter(client);

    // Create an adapter for each data type you need
    export const boolAdapter = adapterFactory<boolean>();
    export const stringAdapter = adapterFactory<string>();
    export const numberAdapter = adapterFactory<number>();
    export const jsonAdapter = adapterFactory<{ foo: string }>();

    export const identify = createIdentify(getContext);
    ```
  </Step>

  <Step title="Use the Correct Adapter">
    When defining your flags, import the native `flag` function from `flags/next` and pass the corresponding typed adapter you created. This ensures the flag's return value and `defaultValue` are correctly typed.

    ```typescript @lib/flagsync/flags {13,22} theme={null}
    import { flag } from 'flags/next';
    import {
      boolAdapter,
      numberAdapter,
      identify,
    } from '@/lib/flagsync';

    // Use the boolAdapter for boolean flags.
    // The return value of killswitchFlag() is now Promise<boolean>.
    export const killswitchFlag = flag({
      identify,
      key: 'my-first-kill-switch',
      adapter: boolAdapter,
      defaultValue: false, // This must be a boolean
    });

    // Use the numberAdapter for number-based flags.
    // The return value of discountFlag() is now Promise<number>.
    export const discountFlag = flag({
      identify,
      key: 'price-discount',
      adapter: numberAdapter,
      defaultValue: 0.1, // This must be a number
    });
    ```
  </Step>

  <Step title="Use in a Server Component">
    Await the flag definition in Server Components:

    ```jsx app/page.tsx theme={null}
    import { killswitchFlag } from '@/lib/flagsync/flags';

    export default async function Page() {
      const value = await killswitchFlag(); // Promise<boolean>

      return (
        <div>The value of {killswitchFlag.key} is {value}</div>
      );
    }
    ```
  </Step>
</Steps>

### Track Events

Submit user actions with the `track` function:

```typescript api/checkout/route.js {18, 27} theme={null}
import { cookies } from 'next/headers';
import { client, identify } from '@/lib/flagsync';
import { api } from '@/lib/api';

export async function POST(request) {
  const { product } = await request.json();

  const cookieStore = cookies(); // ReadonlyRequestCookies
  const headerStore = headers(); // ReadonlyHeaders

  const context = await identify({
    cookies: cookieStore,
    headers: headerStore
  });

  // Property event
  client.track(context, 'purchase-event', null, product);

  // Time the operation
  const t0 = Date.now();
  const order = await api.makePurchase(request);
  const receipt = await api.sendOrder(order)
  const t1 = Date.now()

  // Numeric event
  client.track(context, 'purchase-duration', t1 - t0);

  return Response.json({ success: true });
}
```

<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/js-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>

## 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 context: 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}
    createClient({
      sdkKey: 'your-sdk-key',
      sync: {
        type: 'ws' // Default
      }
    });
    ```
  </Step>

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

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

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

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

  <Step title="Off">
    Disable syncing:

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

### Bootstrapping

Initialize the SDK with a set of bootstrap flags:

```typescript theme={null}
createClient({
  sdkKey: 'your-sdk-key',
  bootstrap: {
    'my-feature': true,
    'other-feature': false,
  },
});
```

## Best Practices

* 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)
