Loading...
Loading...
Comprehensive guide for implementing feature flags and A/B tests using the Flags SDK (the `flags` npm package). Use when: (1) Creating or declaring feature flags with `flag()`, (2) Setting up feature flag providers/adapters (Vercel, Statsig, LaunchDarkly, PostHog, GrowthBook, Hypertune, Edge Config, OpenFeature, Flagsmith, Reflag, Split, Optimizely, or custom adapters), (3) Implementing precompute patterns for static pages with feature flags, (4) Setting up evaluation context with `identify` and `dedupe`, (5) Integrating the Flags Explorer / Vercel Toolbar, (6) Working with feature flags in Next.js (App Router, Pages Router, Middleware) or SvelteKit, (7) Writing custom adapters, (8) Encrypting/decrypting flag values for the toolbar, (9) Any task involving the `flags`, `flags/next`, `flags/sveltekit`, `flags/react`, or `@flags-sdk/*` packages. Triggers on: feature flags, A/B testing, experimentation, flags SDK, flag adapters, precompute flags, Flags Explorer, feature gates, flag overrides.
npx skill4agent add vercel/flags flags-sdkflagsimport { flag } from 'flags/next';
export const exampleFlag = flag({
key: 'example-flag',
decide() { return false; },
});
// Usage: just call the function
const value = await exampleFlag();decideoriginimport { flag } from 'flags/next';
import { statsigAdapter } from '@flags-sdk/statsig';
export const myGate = flag({
key: 'my_gate',
adapter: statsigAdapter.featureGate((gate) => gate.value),
identify,
});import { flag } from 'flags/next'; // or 'flags/sveltekit'
export const showBanner = flag<boolean>({
key: 'show-banner',
description: 'Show promotional banner',
defaultValue: false,
options: [
{ value: false, label: 'Hide' },
{ value: true, label: 'Show' },
],
decide() { return false; },
});identifydecideimport { dedupe, flag } from 'flags/next';
import type { ReadonlyRequestCookies } from 'flags';
interface Entities {
user?: { id: string };
}
const identify = dedupe(
({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
const userId = cookies.get('user-id')?.value;
return { user: userId ? { id: userId } : undefined };
},
);
export const dashboardFlag = flag<boolean, Entities>({
key: 'new-dashboard',
identify,
decide({ entities }) {
if (!entities?.user) return false;
return ['user1', 'user2'].includes(entities.user.id);
},
});import { flag } from 'flags/next';
import { vercelAdapter } from '@flags-sdk/vercel';
export const exampleFlag = flag({
key: 'example-flag',
adapter: vercelAdapter(),
});| Parameter | Type | Description |
|---|---|---|
| | Unique flag identifier |
| | Resolves the flag value |
| | Fallback if |
| | Shown in Flags Explorer |
| | URL to manage the flag in provider dashboard |
| | Possible values, used for precompute + Flags Explorer |
| | Provider adapter implementing |
| | Returns evaluation context (entities) for |
identifydedupeimport { dedupe } from 'flags/next';
const identify = dedupe(({ cookies }) => {
return { user: { id: cookies.get('uid')?.value } };
});dedupe// app/.well-known/vercel/flags/route.ts
import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import * as flags from '../../../../flags';
export const GET = createFlagsDiscoveryEndpoint(async () => {
return getProviderData(flags);
});import { getProviderData, createFlagsDiscoveryEndpoint } from 'flags/next';
import { getProviderData as getStatsigProviderData } from '@flags-sdk/statsig';
import { mergeProviderData } from 'flags';
import * as flags from '../../../../flags';
export const GET = createFlagsDiscoveryEndpoint(async () => {
return mergeProviderData([
getProviderData(flags),
getStatsigProviderData({
consoleApiKey: process.env.STATSIG_CONSOLE_API_KEY,
projectId: process.env.STATSIG_PROJECT_ID,
}),
]);
});// src/hooks.server.ts
import { createHandle } from 'flags/sveltekit';
import { FLAGS_SECRET } from '$env/static/private';
import * as flags from '$lib/flags';
export const handle = createHandle({ secret: FLAGS_SECRET, flags });node -e "console.log(crypto.randomBytes(32).toString('base64url'))"FLAGS_SECRETvc env add FLAGS_SECRETvc env pullprecompute(flagGroup)code/${code}/original-pathcodeawait myFlag(code, flagGroup)origindecideimport type { Adapter } from 'flags';
export function createMyAdapter(/* options */) {
return function myAdapter<ValueType, EntitiesType>(): Adapter<ValueType, EntitiesType> {
return {
origin(key) {
return `https://my-provider.com/flags/${key}`;
},
async decide({ key }): Promise<ValueType> {
// evaluate against your provider
return false as ValueType;
},
};
};
}| Function | Purpose |
|---|---|
| Encrypt resolved flag values |
| Decrypt flag values |
| Encrypt flag definitions/metadata |
| Decrypt flag definitions |
| Encrypt toolbar overrides |
| Decrypt toolbar overrides |
FLAGS_SECRETimport { encryptFlagValues } from 'flags';
import { FlagValues } from 'flags/react';
async function ConfidentialFlags({ values }) {
const encrypted = await encryptFlagValues(values);
return <FlagValues values={encrypted} />;
}import { FlagValues, FlagDefinitions } from 'flags/react';
// Renders script tag with flag values for Flags Explorer
<FlagValues values={{ myFlag: true }} />
// Renders script tag with flag definitions for Flags Explorer
<FlagDefinitions definitions={{ myFlag: { options: [...], description: '...' } }} />flagsflags/reactflags/nextflags/sveltekit