For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: text/markdown.
Usage
Evaluate feature flags on the web client and server, sync targeting context, and add new flag keys to TurboStarter.
Once a provider is active, reading a flag is a one-liner. Use React hooks in Client Components and the @workspace/flags-web/server helpers in Server Components, route handlers, and other server code.
Flag keys
Keys live in one place so every platform stays aligned:
export const Flag = {
DEMO: "demo",
} as const;Import Flag from @workspace/flags and pass the constant (not a raw string) into evaluators. That keeps typos out of production and makes refactors easy.
Client-side evaluation
OpenFeature hooks are re-exported from @workspace/flags:
"use client";
import { Flag, useBooleanFlagValue } from "@workspace/flags";
export const NewCheckoutButton = () => {
const enabled = useBooleanFlagValue(Flag.DEMO, false);
if (!enabled) {
return null;
}
return <button type="button">Try the new checkout</button>;
};Other value types:
| Hook | Typical use |
|---|---|
useBooleanFlagValue | On/off gates |
useStringFlagValue | Variant copy, theme names, URLs |
useNumberFlagValue | Limits, percentages, experiment arms |
useObjectFlagValue | Structured payloads / config blobs |
Always pass a sensible default as the second argument. That value is used while the provider loads, or if evaluation fails.
Server-side evaluation
Web is the only platform package that exposes a server API. Prefer it in RSC pages so gated UI never flashes the wrong state:
import { Flag } from "@workspace/flags";
import { getBooleanValue } from "@workspace/flags-web/server";
const demo = await getBooleanValue(Flag.DEMO, false, {
targetingKey: user.id,
});Available helpers from @workspace/flags-web/server:
getBooleanValue(flag, defaultValue, context?)getStringValue(flag, defaultValue, context?)getNumberValue(flag, defaultValue, context?)getObjectValue(flag, defaultValue, context?)
Pass targetingKey (and optional traits) when the provider needs identity for rules. The demo settings page does exactly that with the signed-in user id.
Targeting context
apps/web/src/lib/providers/flags.tsx already syncs auth state into OpenFeature:
if (session.data?.user) {
const { id, email, name } = session.data.user;
void setContext({ targetingKey: id, email, name });
return;
}
void clearContext();You rarely need to call setContext / clearContext yourself. When you do (for example, org-scoped targeting), import them from @workspace/flags-web.
With PostHog, syncContext maps this to identify / reset so dashboard rules see the same person as your analytics events.
Add a new flag
Declare the key
Add a constant in packages/flags/shared/src/keys.ts:
export const Flag = {
DEMO: "demo",
NEW_CHECKOUT: "NEW_CHECKOUT",
} as const;Update in-memory defaults
Give local development a known value in packages/flags/shared/src/in-memory.ts:
[Flag.NEW_CHECKOUT]: {
disabled: false,
variants: { on: true, off: false },
defaultVariant: "off",
},Create it remotely (if needed)
In PostHog or GrowthBook, create a flag with the same key (NEW_CHECKOUT). Configure rollouts and targeting there.
Evaluate it in the UI
Use a hook on the client or getBooleanValue on the server:
const showCheckout = useBooleanFlagValue(Flag.NEW_CHECKOUT, false);Troubleshooting
| Symptom | What to check |
|---|---|
| Demo banner never appears | In-memory defaultVariant is "on" by default. If you switched to PostHog/GrowthBook, ensure a remote flag named demo exists and is enabled for your user. |
| Client and server disagree | Confirm providers/index.ts, server.ts, and env.ts all point at the same provider. |
| Remote rules ignore the user | Confirm you are signed in so targetingKey is set. Check PostHog person / GrowthBook attributes. |
| Missing env / boot errors | Set NEXT_PUBLIC_POSTHOG_* or NEXT_PUBLIC_GROWTHBOOK_* for the active provider. Restart pnpm dev after changes. |
How is this guide?
Last updated on