Overview
The Flags SDK is a free, open-source library for implementing feature flags and A/B tests. It works with any flag provider or custom setup and is compatible with App Router, Pages Router, and Middleware.
Key benefits:
- Server-side evaluation (no client-side flash)
- Works with the Vercel Toolbar's Flags Explorer
- Type-safe flag definitions
- Zero dependencies on external services
Installation
bun add flagsbun add flagsDeclaring Feature Flags
Create a flags file in the relevant lib folder. Each domain can manage its own flags:
import { flag } from "flags/next";
export const vercelSignInFlag = flag({
key: "vercel-sign-in",
decide() {
// Enable when environment variables are configured
return Boolean(
process.env.VERCEL_CLIENT_ID && process.env.VERCEL_CLIENT_SECRET,
);
},
});import { flag } from "flags/next";
export const vercelSignInFlag = flag({
key: "vercel-sign-in",
decide() {
// Enable when environment variables are configured
return Boolean(
process.env.VERCEL_CLIENT_ID && process.env.VERCEL_CLIENT_SECRET,
);
},
});The decide() function runs on the server and determines the flag's value. You can use:
- Environment variables
- User session data
- Random assignment for A/B tests
- External flag providers
Using Flags in Server Components
Call the flag function directly in your page or component:
import { vercelSignInFlag } from "@/lib/auth/flags";
export default async function SignInPage() {
const showVercelSignIn = await vercelSignInFlag();
return (
<main>
<SignIn showVercelSignIn={showVercelSignIn} />
</main>
);
}import { vercelSignInFlag } from "@/lib/auth/flags";
export default async function SignInPage() {
const showVercelSignIn = await vercelSignInFlag();
return (
<main>
<SignIn showVercelSignIn={showVercelSignIn} />
</main>
);
}Passing Flags to Client Components
Since flags are evaluated server-side, pass the resolved value as a prop:
// Client component receives the flag value as a prop
export function SignIn({ showVercelSignIn }: { showVercelSignIn: boolean }) {
return (
<div>
{showVercelSignIn && (
<Button onClick={handleVercelSignIn}>Sign in with Vercel</Button>
)}
{/* Rest of the form */}
</div>
);
}// Client component receives the flag value as a prop
export function SignIn({ showVercelSignIn }: { showVercelSignIn: boolean }) {
return (
<div>
{showVercelSignIn && (
<Button onClick={handleVercelSignIn}>Sign in with Vercel</Button>
)}
{/* Rest of the form */}
</div>
);
}Flag Patterns
Environment-Based Flags
Enable features based on environment configuration:
export const stripeFlag = flag({
key: "stripe-enabled",
decide() {
return Boolean(process.env.STRIPE_SECRET_KEY);
},
});export const stripeFlag = flag({
key: "stripe-enabled",
decide() {
return Boolean(process.env.STRIPE_SECRET_KEY);
},
});Flags with Validated Config
For flags that depend on validated environment variables, combine with the loadConfig pattern. Create a config file that uses the feature flag pattern:
import { loadConfig } from "@/lib/common/load-config";
export const stripeConfig = loadConfig({
name: "Stripe",
flag: process.env.ENABLE_STRIPE, // Only validate when flag is set
server: {
secretKey: process.env.STRIPE_SECRET_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
},
});
// Type: FeatureConfig<...>import { loadConfig } from "@/lib/common/load-config";
export const stripeConfig = loadConfig({
name: "Stripe",
flag: process.env.ENABLE_STRIPE, // Only validate when flag is set
server: {
secretKey: process.env.STRIPE_SECRET_KEY,
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
},
});
// Type: FeatureConfig<...>Then create a flag that checks the config:
import { flag } from "flags/next";
import { stripeConfig } from "./config";
export const stripeFlag = flag({
key: "stripe-enabled",
decide() {
return stripeConfig.isEnabled;
},
});import { flag } from "flags/next";
import { stripeConfig } from "./config";
export const stripeFlag = flag({
key: "stripe-enabled",
decide() {
return stripeConfig.isEnabled;
},
});This approach validates all required env vars when the feature is enabled, giving clear error messages if any are missing.
Percentage Rollout
Gradually roll out features to a percentage of users:
export const newFeatureFlag = flag({
key: "new-feature",
decide() {
return Math.random() < 0.1; // 10% of requests
},
});export const newFeatureFlag = flag({
key: "new-feature",
decide() {
return Math.random() < 0.1; // 10% of requests
},
});User-Based Flags
Enable features for specific users (requires session context):
export const betaFeatureFlag = flag({
key: "beta-feature",
async decide({ headers }) {
// Access user session from headers if needed
const session = await getSession(headers);
return session?.user?.email?.endsWith("@mycompany.com") ?? false;
},
});export const betaFeatureFlag = flag({
key: "beta-feature",
async decide({ headers }) {
// Access user session from headers if needed
const session = await getSession(headers);
return session?.user?.email?.endsWith("@mycompany.com") ?? false;
},
});Flags Explorer (Vercel Toolbar)
When deployed to Vercel, the Flags Explorer in the Vercel Toolbar lets you override flags for your session without affecting other users.
This is useful for:
- Testing feature states without changing configuration
- QA testing different flag combinations
- Debugging flag-dependent behavior
The Flags SDK automatically respects overrides set by the Flags Explorer.
Project Structure
Organize flags by domain, colocated with related code and config:
src/lib/
├── auth/
│ ├── config.ts # Auth environment config
│ ├── flags.ts # Auth-related flags
│ ├── client.ts
│ └── server.tsx
├── stripe/
│ ├── config.ts # Stripe environment config (with flag)
│ ├── flags.ts # Stripe-related flags
│ └── client.ts
└── common/
└── load-config.ts # Shared config loadersrc/lib/
├── auth/
│ ├── config.ts # Auth environment config
│ ├── flags.ts # Auth-related flags
│ ├── client.ts
│ └── server.tsx
├── stripe/
│ ├── config.ts # Stripe environment config (with flag)
│ ├── flags.ts # Stripe-related flags
│ └── client.ts
└── common/
└── load-config.ts # Shared config loader