# The Flags SDK adapter.

`@dif.sh/flags-sdk` is an adapter for the [Flags SDK](https://flags-sdk.dev/), the `flags` package used by most Next.js apps. Your `flag()` declarations resolve from the same `.md` files and the same generated client as the rest of dif, and evaluating a flag still makes no network call.

Reach for it when the app already declares flags with `flags/next` or `flags/sveltekit`, or when you want flags to show up in Vercel's Flags Explorer. If you don't use the Flags SDK, call [`dif()`](https://dif.sh/docs/sdk/#dif) or [`assign()`](https://dif.sh/docs/sdk/#assign) directly instead — the typed client is the shorter path.

The package exports two things:

-   `difAdapter` and `createDifAdapter`, the [adapter instance](#declare).
-   `getProviderData`, for the [Flags Explorer](#explorer).

## Install

```
npm install flags @dif.sh/flags-sdk @dif.sh/sdk
npm install -D @dif.sh/cli
```

> // note `@dif.sh/sdk` is a peer dependency, and your install must hold exactly one copy of it. The generated client registers experiments into a module-level registry inside the SDK, and the adapter reads that same registry — a second nested copy resolves to an empty one, and every flag falls back to its `defaultValue`.

## Declare flags

A flag is a `.md` file like any other dif experiment. Scaffold the workspace and draft one:

```
npx @dif.sh/cli init
npx @dif.sh/cli new new-checkout --surface checkout
```

Declare the variants and set `status: active`. The first variant is the fallback; raise the second one's `weight` toward 100 to roll the flag out:

```
# dif/experiments/active/new-checkout.md
id: new-checkout
status: active
owner: ada@acme.dev
surface: checkout
variants:
  - id: "off"
    weight: 90
  - id: "on"
    weight: 10
metrics:
  primary: completed_checkout
```

`dif build` compiles the active files into `dif/generated/client.ts`, which `dif init` gitignores. Regenerate it before every build:

```
// package.json
"scripts": { "prebuild": "dif build" }
```

Import the generated client once in `flags.ts`, then declare the flags. The flag `key` is the experiment `id`:

```
// flags.ts
import "./dif/generated/client";
import { flag } from "flags/next";
import { difAdapter } from "@dif.sh/flags-sdk";

export const newCheckoutFlag = flag<boolean>({
  key: "new-checkout",
  defaultValue: false,
  adapter: difAdapter.isEnabled(),
});

export const heroCtaFlag = flag<string>({
  key: "home-hero-cta",
  defaultValue: "control",
  adapter: difAdapter.variant(),
});
```

-   `difAdapter.variant()` resolves to the assigned variant id, such as `control` or `variant_a`.
-   `difAdapter.isEnabled()` resolves to `true` when the assigned variant is not the first declared one. `difAdapter.isEnabled({ on: "variant_b" })` is `true` only for `variant_b`.

Then call the flag where you render:

```
// app/page.tsx
import { newCheckoutFlag } from "@/flags";

export default async function Page() {
  const newCheckout = await newCheckoutFlag();
  return newCheckout ? <OnePageCheckout /> : <Checkout />;
}
```

## Adapter options

`difAdapter` is a default instance. Use `createDifAdapter` to change its defaults:

```
// flags.ts
import { createDifAdapter } from "@dif.sh/flags-sdk";

const difAdapter = createDifAdapter({
  overrides: process.env.VERCEL_ENV !== "production",
  origin: (key) =>
    `https://github.com/acme/app/blob/main/dif/experiments/active/${key}.md`,
});
```

| option | default | notes |
| --- | --- | --- |
| `cookieName` | `dif_uid` | Cookie the default `identify` reads the user id from. |
| `overrides` | `true` | Honor the `_dif` preview cookie. `dif qa --user u_1 --force new-checkout=on` prints the matching `?_dif=` link. |
| `onExposure` | `—` | Called with `{ key, variant, bucket, userId }` when a request is bucketed. See [exposures](#exposures). |
| `origin` | `—` | URL the Flags Explorer links each flag to — point it at the `.md` file. |

## Identify users

`identify` runs during rendering, where cookies can't be set, so mint the anonymous id in middleware. Setting it on the forwarded request lets the first render read it; setting it on the response persists it:

```
// middleware.ts — on Next.js 16, name the file proxy.ts and export `proxy`
import { type NextRequest, NextResponse } from "next/server";
import { DIF_OVERRIDES_COOKIE, DIF_UID_COOKIE } from "@dif.sh/flags-sdk";

export function middleware(request: NextRequest) {
  const uid = request.cookies.get(DIF_UID_COOKIE)?.value ?? crypto.randomUUID();
  const force = request.nextUrl.searchParams.get(DIF_OVERRIDES_COOKIE);

  // forward on the request so identify() sees them during this render
  request.cookies.set(DIF_UID_COOKIE, uid);
  if (force !== null) request.cookies.set(DIF_OVERRIDES_COOKIE, force);

  const response = NextResponse.next({ request: { headers: request.headers } });
  response.cookies.set(DIF_UID_COOKIE, uid, {
    path: "/", sameSite: "lax", maxAge: 60 * 60 * 24 * 365,
  });
  return response;
}
```

To bucket signed-in users on their account id, or to pass the attributes an experiment's [`audience`](https://dif.sh/docs/format/#audiences) block checks, supply your own `identify`:

```
// flags.ts
const identify = dedupe((async ({ headers, cookies }) => ({
  userId: session?.userId ?? cookies.get(DIF_UID_COOKIE)?.value ?? null,
  attributes: { plan: session?.plan ?? "free" },
})) satisfies Identify<DifEntities>);
```

> // note The default `identify` passes no attributes, so an experiment with an `audience` block always serves its fallback until you provide one. Attribute names must match the `audience_attributes` declared in [`dif/config.yaml`](https://dif.sh/docs/config/).

## Fallbacks

For a flag whose variants are `off` and `on`:

| request | `variant()` | `isEnabled()` | exposure |
| --- | --- | --- | --- |
| user id set, audience matches | bucketed variant | `variant !== "off"` | fires |
| no user id | `"off"` | `false` | — |
| audience miss, or exclusion-group loss | `"off"` | `false` | — |
| `_dif` preview force | forced variant | `true` | — |
| Flags Explorer override | override value | override value | — |
| id missing from the generated client | `decide` throws → `defaultValue` | — |

When `decide` throws, the Flags SDK returns the flag's `defaultValue` and logs the error; a flag declared without one rejects. The message names the fix: import `./dif/generated/client` in `flags.ts`, run `dif build`, and check the file is `status: active`.

## Exposures

dif analyses an experiment from exposure events: one per user bucketed into a variant. The adapter assigns on the server and fires no exposure of its own, so pass `onExposure` to record them:

```
// flags.ts
import { waitUntil } from "@vercel/functions";

const difAdapter = createDifAdapter({
  onExposure: (exposure) => waitUntil(sendToAnalytics("dif.exposure", exposure)),
});
```

-   It runs once per flag per request and keeps no state across requests. Deduplicate on `(key, userId)` in your pipeline.
-   It isn't awaited. Errors are logged and never change the flag value, so wrap network calls in `waitUntil`.
-   Next.js renders prefetched routes, so an exposure can fire for a page the visitor never opens.

> // note There is no server-side delivery path to dif.sh Cloud yet: [`DifServer`](https://dif.sh/docs/sdk/#server) sends metrics, not exposures. Forward `onExposure` to your own pipeline, or record the exposure in the browser with `recordExposure()` from `@dif.sh/sdk`.

## Flags Explorer

`getProviderData` emits every registered experiment for Vercel's Flags Explorer. Importing `flags.ts` also imports the generated client, which fills the registry it reads. Passing the `flags` exports gives `isEnabled()` flags `false`/`true` options instead of variant ids:

```
// app/.well-known/vercel/flags/route.ts
import { createFlagsDiscoveryEndpoint } from "flags/next";
import { getProviderData } from "@dif.sh/flags-sdk";
import * as flags from "../../../../flags";

export const GET = createFlagsDiscoveryEndpoint(() => getProviderData({ flags }));
```

Each definition's description carries the experiment's surface, variant weights, fallback variant, and exclusion group.

invariant the adapter never evaluates a flag over the network: it reads `dif/generated/client.ts`, so a flag resolves from the commit that's deployed.
