Fargo Flags Documentation
Complete guide to the enhanced feature flags toolkit built on Vercel's Flags SDK.
Fargo Flags Documentation
Complete guide to the enhanced feature flags toolkit built on Vercel's Flags SDK.
1. Overview
Fargo Flags is a streamlined toolkit built on top of Vercel's Flags SDK that adds enhanced developer experience, CLI tooling, and component registry distribution. It embraces the Flags SDK's "flags as code" principles while making them easier to adopt and scale.
Built on Solid Foundation
We leverage Vercel's Flags SDK core principles:
- Flags as code: Declarative definitions with consistent call sites
- Server-side resolution: Secure, performant flag evaluation
- Type safety: Full TypeScript support with runtime validation
- No vendor lock-in: Your flag logic stays in your codebase
Enhanced Developer Experience
Fargo Flags adds powerful tooling on top of this foundation:
- Interactive CLI wizard: Guided flag creation without manual boilerplate
- Automatic registry management: No need to manually maintain imports
- Component registry distribution: Install via shadcn/ui-style commands
- Enhanced React integration: Providers, hooks, and conditional components
- Testing utilities: Easy flag overrides for development and QA
- Consistency validation: Catch configuration drift in CI/CD
2. Installation
Core System
Install the core Fargo Flags system using the shadcn CLI:
📦 Core Installation Includes
- •
src/lib/flags/kit.ts- Core types and defineFlag helper - •
src/lib/flags/runtime.ts- Server-side resolver + client serialization - •
src/components/flags/flags-provider.tsx- React context provider - •
src/lib/flags/registry.config.ts- Starter registry with anchor tags - •
src/lib/flags/defs/.gitkeep- Empty directory for flag definitions
Optional Components
Install additional components as needed:
Flag Component
Installs <Flag> conditional rendering component
Test Provider
Installs FlagsTestProvider for testing
CLI Tools
🛠️ CLI Installation Includes
- •
scripts/create-flag.ts- Interactive flag creation wizard - •
scripts/check-flags-registry.ts- Consistency validation tool - • Instructions to add npm scripts to your
package.json
📋 Required Package.json Scripts
After installing the CLI tools, add these scripts to your package.json:
{
"scripts": {
"flags:new": "tsx scripts/create-flag.ts",
"flags:check": "tsx scripts/check-flags-registry.ts",
"flags:validate": "tsx scripts/validate-flags-installation.ts"
}
}💡 The shadcn CLI cannot automatically modify package.json, so this step is manual.
Manual Dependencies
If not using the registry, install dependencies manually:
Runtime Dependencies
Development Tools (for CLI)
Package.json Scripts
{
"scripts": {
"flags:new": "tsx scripts/create-flag.ts",
"flags:check": "tsx scripts/check-flags-registry.ts",
"flags:validate": "tsx scripts/validate-flags-installation.ts"
}
}⚠️ Important Notes
- • The shadcn CLI cannot automatically update
package.jsonscripts - • You must manually add the
flags:new,flags:check, andflags:validatescripts - • CLI tools require
tsxto run TypeScript files directly - • Prettier is optional but recommended for code formatting
3. Quick Start
1. Set up the core files
Copy the core system files into your project structure:
src/
├── lib/flags/
│ ├── kit.ts # Core types
│ ├── runtime.ts # Server resolver
│ ├── registry.config.ts # Flag registry
│ └── defs/ # Flag definitions
├── components/flags/
│ ├── flags-provider.tsx # React context
│ ├── flag.tsx # Conditional component
│ └── flags-test-provider.tsx # Testing utilities
└── scripts/
├── create-flag.ts # Flag wizard
└── check-flags-registry.ts # Consistency checker2. Integrate with your app
3. Install CLI Tools
This installs the flag creation wizard and consistency checker scripts.
💡 Manual Step Required
Add these scripts to your package.json:
"scripts": {
"flags:new": "tsx scripts/create-flag.ts",
"flags:check": "tsx scripts/check-flags-registry.ts"
}4. Create your first flag
The interactive wizard will guide you through creating a properly typed flag with automatic registry updates.
5. Validate your setup
4. Defining Flags
Flag File Structure
Each flag is defined in its own file in src/lib/flags/defs/:
Boolean Flag
// src/lib/flags/defs/my-feature.flag.ts
import { z } from "zod";
import { defineFlag } from "../kit";
export const key = "my-awesome-feature" as const;
export const schema = z.boolean();
export default defineFlag({
key,
schema,
description: "Enable my awesome new feature",
defaultValue: false,
client: { public: true }, // Expose to client
async decide(ctx) {
const user = await ctx.getUser?.();
return user?.plan === "premium";
},
});Enum Flag
// src/lib/flags/defs/theme-mode.flag.ts
import { z } from "zod";
import { defineFlag } from "../kit";
export const key = "theme-mode" as const;
export const schema = z.enum(["light", "dark", "auto"]);
export default defineFlag({
key,
schema,
description: "Application theme mode",
defaultValue: "light",
options: [
{ value: "light", label: "Light Mode" },
{ value: "dark", label: "Dark Mode" },
{ value: "auto", label: "Auto (System)" }
],
client: { public: true },
});Server-Only Flag
// src/lib/flags/defs/ai-model.flag.ts
import { z } from "zod";
import { defineFlag } from "../kit";
export const key = "ai-claims-model" as const;
export const schema = z.enum([
"gpt-4o-mini",
"gpt-4.5",
"claude-3-sonnet"
]);
export default defineFlag({
key,
schema,
description: "Which AI model to use for claims processing",
defaultValue: "gpt-4o-mini",
client: { public: false }, // Server-only
async decide(ctx) {
// Complex server-side logic
const workspace = await ctx.getWorkspace?.();
return workspace?.plan === "enterprise" ? "gpt-4.5" : "gpt-4o-mini";
},
});5. Understanding resolveAllFlags
resolveAllFlags is the server-side engine that evaluates all your feature flags and returns their resolved values. It's the bridge between your flag definitions and your application.
How It Works
export async function resolveAllFlags(ctx?: FlagContext): Promise<Flags> {
const keys = Object.keys(registry) as (keyof SchemaMap)[];
const entries = await Promise.all(
keys.map(async (key) => {
const def = registry[key];
// 🎯 This is where the magic happens:
const raw = await Promise.resolve(def.decide?.(ctx) ?? def.defaultValue);
const value = flagSchemas[key].parse(raw); // Zod validation
return [key, value] as const;
})
);
return Object.fromEntries(entries) as Flags;
}Step-by-Step Process
- Gets all flag keys from your registry
- Runs in parallel - all flags resolve simultaneously for performance
- For each flag:
- Calls the
decide()function (if defined) with context - Falls back to
defaultValueif nodecide()function - Validates the result against the Zod schema
- Returns the final resolved value
- Calls the
Usage Patterns
Basic Usage (No Context)
// Simple flags that don't need user/workspace data const flags = await resolveAllFlags(); // All flags use their defaultValue or simple decide() logic
With Context (Recommended)
// Flags that need user/workspace information for decisions
const flags = await resolveAllFlags({
getUser: async () => getCurrentUser(),
getWorkspace: async () => getCurrentWorkspace(),
});In Next.js App Router (Primary Use Case)
// app/layout.tsx
export default async function RootLayout({ children }) {
// 🚀 This runs on every request
const serverFlags = await resolveAllFlags({
getUser: async () => {
const session = await getServerSession();
return session?.user || null;
},
getWorkspace: async () => {
const workspaceId = headers().get('x-workspace-id');
return workspaceId ? await getWorkspace(workspaceId) : null;
},
});
const clientFlags = pickClientFlags(serverFlags);
return (
<html>
<body>
<FlagsProvider flags={clientFlags}>
{children}
</FlagsProvider>
</body>
</html>
);
}Key Benefits
🛡️ Server-Side Resolution
- Security: Sensitive logic stays on the server
- Performance: Complex decisions don't slow down the client
- Consistency: Same flag values across the entire request
⚡ Parallel Execution
- All flags resolve simultaneously - not sequentially
- Optimal performance for multiple flags
- Efficient use of server resources
Advanced Patterns
Error Handling
const flags = await resolveAllFlags({
getUser: async () => {
try {
return await getCurrentUser();
} catch (error) {
console.warn("User lookup failed, using defaults");
return null; // Flags will use defaultValue
}
},
});Caching for Performance
import { cache } from "react";
// Cache expensive operations per request
const getCachedUser = cache(async () => {
return await expensiveUserLookup();
});
const flags = await resolveAllFlags({
getUser: getCachedUser, // Only runs once per request
});6. Using Flags
useFlag Hook
import { useFlag } from "@/components/flags/flags-provider";
function MyComponent() {
const isEnabled = useFlag("my-awesome-feature");
const themeMode = useFlag("theme-mode");
return (
<div>
{isEnabled && <NewFeature />}
<div className={themeMode === "dark" ? "dark-theme" : "light-theme"}>
Content
</div>
</div>
);
}Flag Component
import { Flag } from "@/components/flags/flag";
function MyComponent() {
return (
<div>
{/* Simple boolean check */}
<Flag when="my-awesome-feature">
<NewFeature />
</Flag>
{/* Negation */}
<Flag when="my-awesome-feature" not={true}>
<OldFeature />
</Flag>
{/* Specific value check */}
<Flag when="theme-mode" is="dark">
<DarkModeStyles />
</Flag>
{/* With fallback */}
<Flag when="loading-state" fallback={<Spinner />}>
<Content />
</Flag>
</div>
);
}Server-Side Usage
// In server components or API routes
import { resolveAllFlags } from "@/lib/flags/runtime";
export async function GET() {
const flags = await resolveAllFlags({
getUser: async () => getCurrentUser(),
getWorkspace: async () => getCurrentWorkspace(),
});
const aiModel = flags["ai-claims-model"];
// Use server-only flag value
return Response.json({ model: aiModel });
}7. Components
FlagsProvider
The main context provider that makes flag values available throughout your application. This component integrates with Next.js App Router to provide SSR-first flag resolution with client-side hydration.
Props
flags:ClientFlags- The client-safe flag values frompickClientFlags()children:React.ReactNode- Your app components
Next.js App Router Integration
// app/layout.tsx (Server Component)
import { ReactNode } from "react";
import { resolveAllFlags, pickClientFlags } from "@/lib/flags/runtime";
import { FlagsProvider } from "@/components/flags/flags-provider";
export default async function RootLayout({
children
}: {
children: ReactNode
}) {
// 1. Resolve ALL flags on the server (including server-only flags)
const serverFlags = await resolveAllFlags({
// Optional context for flag decision logic
getUser: async () => {
// Your user fetching logic
return await getCurrentUser();
},
getWorkspace: async () => {
// Your workspace fetching logic
return await getCurrentWorkspace();
},
});
// 2. Extract only client-safe flags (respects client.public setting)
const clientFlags = pickClientFlags(serverFlags);
return (
<html lang="en">
<body>
<FlagsProvider flags={clientFlags}>
{children}
</FlagsProvider>
</body>
</html>
);
}📦 Key Features
- Server-side execution: All
decide()functions run on the server - Context support: Pass user/workspace data for personalized flags
- Parallel resolution: All flags are resolved concurrently for performance
- Schema validation: Flag values are validated against Zod schemas
useFlag Hook
A React hook that returns the current value of a specific flag. Provides full TypeScript autocomplete and type safety.
Signature
function useFlag<K extends keyof ClientFlags>(key: K): ClientFlags[K]
Examples
import { useFlag } from "@/components/flags/flags-provider";
function FeatureComponent() {
// Boolean flag
const isNewDashboard = useFlag("enable-new-dashboard");
// Enum flag
const themeMode = useFlag("theme-mode");
// String flag
const apiEndpoint = useFlag("api-endpoint-version");
return (
<div>
{isNewDashboard && <NewDashboard />}
<div className={`theme-${themeMode}`}>
<ApiClient endpoint={apiEndpoint} />
</div>
</div>
);
}Flag Component
A declarative component for conditional rendering based on flag values. Provides a clean, readable way to show/hide content without cluttering your JSX with conditionals.
Boolean Flags
import { Flag } from "@/components/flags/flag";
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
{/* Show when flag is truthy */}
<Flag when="enable-analytics">
<AnalyticsPanel />
</Flag>
{/* Show when flag is falsy */}
<Flag when="enable-analytics" not={true}>
<div>Analytics coming soon!</div>
</Flag>
{/* With fallback content */}
<Flag
when="enable-premium-features"
fallback={<UpgradePrompt />}
>
<PremiumFeatures />
</Flag>
</div>
);
}FlagsTestProvider
A specialized provider for testing and development that allows you to override flag values. Perfect for unit tests, integration tests, and Storybook stories.
Unit Testing
import { render, screen } from "@testing-library/react";
import { FlagsTestProvider } from "@/components/flags/flags-test-provider";
import Dashboard from "./Dashboard";
describe("Dashboard", () => {
test("shows analytics when flag is enabled", () => {
render(
<FlagsTestProvider overrides={{ "enable-analytics": true }}>
<Dashboard />
</FlagsTestProvider>
);
expect(screen.getByText("Analytics Panel")).toBeInTheDocument();
});
});8. Testing
Unit Tests
import { render } from "@testing-library/react";
import { FlagsTestProvider } from "@/components/flags/flags-test-provider";
import MyComponent from "./MyComponent";
test("shows new feature when flag is enabled", () => {
render(
<FlagsTestProvider overrides={{ "my-feature": true }}>
<MyComponent />
</FlagsTestProvider>
);
expect(screen.getByText("New Feature")).toBeInTheDocument();
});Storybook
// MyComponent.stories.tsx
export const WithFeatureEnabled = {
decorators: [
(Story) => (
<FlagsTestProvider overrides={{ "my-feature": true }}>
<Story />
</FlagsTestProvider>
),
],
};
export const WithFeatureDisabled = {
decorators: [
(Story) => (
<FlagsTestProvider overrides={{ "my-feature": false }}>
<Story />
</FlagsTestProvider>
),
],
};9. CLI Tools
Fargo Flags includes powerful CLI tools to streamline flag management. These tools are distributed via the shadcn registry and provide an interactive wizard for creating flags and a consistency checker for CI/CD pipelines.
Installation & Setup
1. Install CLI Tools
This installs scripts/create-flag.ts and scripts/check-flags-registry.ts
2. Add Package.json Scripts
The shadcn CLI cannot modify package.json automatically, so add these scripts manually:
{
"scripts": {
"flags:new": "tsx scripts/create-flag.ts",
"flags:check": "tsx scripts/check-flags-registry.ts",
"flags:validate": "tsx scripts/validate-flags-installation.ts"
}
}3. Install Dependencies
The CLI tools require these development dependencies:
flags:new - Interactive Flag Creation
The flag creation wizard guides you through defining new feature flags with proper TypeScript types, Zod schemas, and automatic registry updates.
Usage
Interactive Prompts
Flag Key
Enter a kebab-case identifier (e.g., enable-ai-assistant)
✓ pagination-ui-location
✗ EnableAIAssistant
✗ enable_ai_assistant
Value Type
Choose between:
- • boolean - Simple on/off flags
- • string enum - Multiple predefined options
Client Exposure
Choose whether this flag should be available on the client-side:
- • Public: Available via
useFlag()and<Flag> - • Server-only: Only available in
resolveAllFlags()
📦 Automatic File Generation
- Flag Definition:
src/lib/flags/defs/your-flag.flag.tsComplete flag definition with schema, defaults, and client settings - Registry Updates:
src/lib/flags/registry.config.tsAutomatic import, schema registration, and client key management - Code Formatting: Prettier formatting (if available)Clean, consistent code style
flags:check - Registry Consistency Validation
The consistency checker validates that your flag definitions and registry are in sync. This is essential for CI/CD pipelines to catch configuration drift.
Usage
pnpm flags:checkWhat It Validates
Registry Completeness
- • All flag files have registry entries
- • All registry entries have corresponding files
- • Schema definitions match registry keys
Client Flag Consistency
- • Public flags are in clientFlagKeys array
- • clientFlagKeys only contains public flags
- • No orphaned client keys
File Structure
- • All .flag.ts files export required 'key'
- • Flag keys match filename conventions
- • No duplicate flag keys
Import Integrity
- • All imports resolve correctly
- • No missing or broken imports
- • Registry anchor tags are intact
Success Output
✔ flags:check OK — 4 registered, 4 files, 3 client-exposedError Output
Defs present but missing in registry.config: - new-experimental-flag Public flags in files but missing from clientFlagKeys: - enable-ai-assistant-in-pdf-toolbar
CI/CD Integration
Add the consistency checker to your CI pipeline to catch flag configuration issues early.
GitHub Actions
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: pnpm install
- run: pnpm flags:check # Validate flag consistency
- run: pnpm build
- run: pnpm test💡 Pro Tips
- • Use the wizard for speed and consistency
- • Run
flags:checkbefore committing changes - • Set up pre-commit hooks to validate flags automatically
- • Use descriptive flag keys that explain the feature
- • Keep flag descriptions up to date for team clarity
10. Architecture
How It Works
- Define flags in individual
*.flag.tsfiles - Wizard updates
registry.config.tswith static imports - Server resolves all flags during SSR with optional context
- Client receives safe subset via React provider
- Components use hooks or
<Flag>wrapper
File Structure
src/
├── lib/flags/
│ ├── kit.ts # Core types and defineFlag helper
│ ├── runtime.ts # Server-side resolver + client serialization
│ ├── registry.config.ts # Aggregator (checked in; wizard updates)
│ └── defs/ # One file per flag
│ ├── feature-a.flag.ts
│ └── feature-b.flag.ts
├── components/flags/
│ ├── flags-provider.tsx # React context provider
│ ├── flag.tsx # Conditional rendering component
│ └── flags-test-provider.tsx # Testing utilities
└── scripts/
├── create-flag.ts # Flag scaffolding wizard
└── check-flags-registry.ts # CI consistency checker11. Best Practices
Flag Naming
- Use kebab-case for flag keys:
enable-new-dashboard - Be descriptive:
show-premium-featuresvspremium - Include context:
checkout-flow-v2vsv2
Security
- Keep sensitive flags server-only:
client: { public: false } - Use
serializeto sanitize public flag values - Never expose API keys or secrets in flag values
Performance
- Keep
decide()functions fast - they run on every request - Use Next.js
cache()for expensive flag decisions - Minimize the number of client-exposed flags
Testing
- Test both enabled and disabled states of features
- Use
FlagsTestProviderfor consistent test environments - Include flag states in your Storybook stories
12. Troubleshooting
Common Issues
Flag not found error
Error: Flag "my-flag" not found
Run pnpm flags:check to ensure the flag is properly registered.
TypeScript errors
Property does not exist on type
Restart your TypeScript server after adding new flags.
Hydration mismatches
Server and client flag values differ
Ensure flag decisions are deterministic or use server-only flags.
13. API Reference
defineFlag()
defineFlag({
key: string; // Unique flag identifier
schema: ZodSchema; // Zod schema for validation
description?: string; // Human-readable description
defaultValue: T; // Default value (must match schema)
options?: Array<{ // For enum flags
value: T;
label?: string;
}>;
client?: { // Client exposure settings
public: boolean;
serialize?: (value: T) => any;
};
decide?: (ctx: FlagContext) => T | Promise<T>; // Server-side decision logic
})useFlag()
function useFlag<K extends keyof ClientFlags>(key: K): ClientFlags[K]
Returns the current value of a client-exposed flag.
resolveAllFlags()
function resolveAllFlags(ctx?: FlagContext): Promise<Flags>
Resolves all flags on the server with optional context.
FlagContext
type FlagContext = {
getUser?: () => Promise<{ id: string; plan?: string } | null>;
getWorkspace?: () => Promise<{ id: string; plan?: string } | null>;
}Context object passed to flag decision functions.