A streamlined toolkit built on top of Vercel's Flags SDK that adds shadcn/ui-compatible component registry distribution, interactive CLI tools, and enhanced developer experience for type-safe feature flags in Next.js.
Fargo Flags is a thin, DX-focused layer on top of Vercel's Flags SDK. We embrace the Flags SDKβs proven patterns, and add tooling that makes them effortless to adopt at scale.
What we embrace from the Flags SDK:
- Flags as code: declarative definitions with consistent call sites
- Server-side resolution: secure, performant evaluation during SSR
- Type safety: full TypeScript support with runtime validation
What we add for a better DX:
- π― One-file-per-flag architecture with individual flag definitions
- π οΈ Interactive CLI wizard for guided flag creation with automatic registry updates
- π Consistency checker to validate the registry in CI/CD
- π¦ shadcn/ui-compatible registry for drop-in installation
- π§ͺ Testing utilities for easy overrides in tests and Storybook
- π¨ React components:
<FlagsProvider>,<Flag>, and hooks - π Comprehensive docs with an interactive demo
- π§ Zero build step via static imports and a checked-in aggregator
Think of it as βFlags SDK with batteries includedβ β same great foundation, streamlined developer experience.
ββββββββββββββββββββββββ define one file per flag ββββββββββββββββββββββββββββ
β src/lib/flags/defs β ββββββββββββββββββββββββββββββββΆ β src/lib/flags/defs/*.ts β
ββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β wizard updates β
β (no codegen/build) βΌ
β ββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββΆβ src/lib/flags/registry β
β .config.ts β
ββββββββββββββββββββββββββββ
β
server resolves β all
βΌ
ββββββββββββββββββββββββββββ
β resolveAllFlags(ctx) β
ββββββββββββββββββββββββββββ
β
filter client-safe keys β
βΌ
ββββββββββββββββββββββββββββ
β pickClientFlags() β
ββββββββββββββββββββββββββββ
β
hydrate into React context
βΌ
ββββββββββββββββββββββββββββ
β <FlagsProvider> β
ββββββββββββββββββββββββββββ
β
use in components β
βΌ
ββββββββββββββββββββββββββ ββββββββββββββββββββββ
β useFlag('key') β β <Flag when="key"/> β
ββββββββββββββββββββββββββ ββββββββββββββββββββββ
Flow
- Define: Add
*.flag.tsfiles tosrc/lib/flags/defs/. - Registry: Wizard updates
src/lib/flags/registry.config.ts(no build step). - Resolve: Server resolves via
resolveAllFlags(ctx);pickClientFlags()filters public flags. - Use: Hydrate
<FlagsProvider>; consume viauseFlag('key')or<Flag when="key" />.
npx shadcn@latest add https://flags.griffen.codes/r/flags-coreThis installs the core flag system including types, runtime, and React provider.
npx shadcn@latest add https://flags.griffen.codes/r/flags-cliAdd these scripts to your package.json:
{
"scripts": {
"flags:new": "tsx scripts/create-flag.ts",
"flags:check": "tsx scripts/check-flags-registry.ts"
}
}// app/layout.tsx
import { resolveAllFlags, pickClientFlags } from "@/lib/flags/runtime";
import { FlagsProvider } from "@/components/flags/flags-provider";
export default async function RootLayout({ children }) {
const serverFlags = await resolveAllFlags({
getUser: async () => getCurrentUser(),
getWorkspace: async () => getCurrentWorkspace(),
});
const clientFlags = pickClientFlags(serverFlags);
return (
<html>
<body>
<FlagsProvider flags={clientFlags}>
{children}
</FlagsProvider>
</body>
</html>
);
}pnpm flags:newFollow the interactive wizard to create a type-safe feature flag with automatic registry updates.
import { useFlag } from "@/components/flags/flags-provider";
import { Flag } from "@/components/flags/flag";
function MyComponent() {
const isEnabled = useFlag("my-feature-flag");
return (
<div>
{/* Using the hook */}
{isEnabled && <NewFeature />}
{/* Using the component */}
<Flag when="my-feature-flag">
<NewFeature />
</Flag>
</div>
);
}Fargo Flags follows the shadcn/ui component registry pattern for easy installation:
| Component | Description | Install Command |
|---|---|---|
| flags-core | Core system (types, runtime, provider) | npx shadcn@latest add https://flags.griffen.codes/r/flags-core |
| flags-flag | <Flag> conditional rendering component |
npx shadcn@latest add https://flags.griffen.codes/r/flags-flag |
| flags-test-provider | Testing utilities for overrides | npx shadcn@latest add https://flags.griffen.codes/r/flags-test-provider |
| flags-cli | Interactive wizard and consistency checker | npx shadcn@latest add https://flags.griffen.codes/r/flags-cli |
- Server-only logic: Flag decision functions run on the server. Avoid client-only APIs in
decide(). - No call-site arguments: Flags are called by key; input comes from the provided context only.
- Client exposure: Only keys marked as public are sent to the client via
pickClientFlags(). - Schema required: Every flag must have a Zod schema and a
defaultValuethat conforms to it.
- What frameworks does this target?
- Next.js App Router. Other environments can adapt the patterns, but examples focus on Next.js.
- Is this a replacement for Vercelβs Flags SDK?
- No. Itβs a thin layer on top that adds CLI workflows, a component registry, and a better DX.
- Can I remove Fargo Flags later?
- Yes. Your flags follow the same patterns; remove the CLI and maintain the registry manually.
- Does it require a build step?
- No. The registry uses checked-in static imports that your app consumes directly.
src/
βββ lib/flags/
β βββ kit.ts # Core types + defineFlag helper
β βββ runtime.ts # Server resolver + client serialization
β βββ registry.config.ts # Flag registry (auto-updated by wizard)
β βββ defs/ # Individual flag definitions
β βββ 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 # Interactive flag creation wizard
βββ check-flags-registry.ts # Registry consistency validator
The CLI tools streamline flag management and ensure consistency:
pnpm flags:new- Guided prompts for flag configuration
- Automatic TypeScript generation
- Registry updates with proper imports
- Code formatting with Prettier
pnpm flags:check- Validates registry completeness
- Checks client flag consistency
- Perfect for CI/CD pipelines
- Catches configuration drift early
pnpm flags:new- Create a new feature flagpnpm flags:check- Validate flag registry consistencypnpm dev- Start development serverpnpm build- Build for production
Use the interactive wizard to create flags, or manually create files in src/lib/flags/defs/:
// 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-side
async decide(ctx) {
// Server-side decision logic
const user = await ctx.getUser?.();
const workspace = await ctx.getWorkspace?.();
return user?.plan === "premium" || workspace?.plan === "enterprise";
},
});Boolean Flags
export const schema = z.boolean();
export default defineFlag({
key: "enable-feature",
schema,
defaultValue: false,
// ...
});Enum Flags
export const schema = z.enum(["light", "dark", "auto"]);
export default defineFlag({
key: "theme-mode",
schema,
defaultValue: "light",
options: [
{ value: "light", label: "Light Mode" },
{ value: "dark", label: "Dark Mode" },
{ value: "auto", label: "Auto (System)" }
],
// ...
});Server-Only Flags
export default defineFlag({
key: "ai-model-selection",
schema: z.enum(["gpt-4o-mini", "gpt-4.5", "claude-3-sonnet"]),
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";
},
});import { useFlag } from "@/components/flags/flags-provider";
function MyComponent() {
const isEnabled = useFlag("my-awesome-feature");
const themeMode = useFlag("theme-mode");
return (
<div className={themeMode === "dark" ? "dark-theme" : "light-theme"}>
{isEnabled ? <NewFeature /> : <OldFeature />}
</div>
);
}import { Flag } from "@/components/flags/flag";
function MyComponent() {
return (
<div>
{/* Boolean flag check */}
<Flag when="my-awesome-feature">
<NewFeature />
</Flag>
{/* Negation */}
<Flag when="my-awesome-feature" not={true}>
<OldFeature />
</Flag>
{/* Enum value check */}
<Flag when="theme-mode" is="dark">
<DarkModeStyles />
</Flag>
{/* With fallback */}
<Flag when="loading-state" fallback={<Spinner />}>
<Content />
</Flag>
</div>
);
}// In server components, API routes, or middleware
import { resolveAllFlags } from "@/lib/flags/runtime";
export async function GET() {
const flags = await resolveAllFlags({
getUser: async () => getCurrentUser(),
getWorkspace: async () => getCurrentWorkspace(),
});
const aiModel = flags["ai-model-selection"]; // Server-only flag
// Use flag value for server-side logic
return Response.json({ model: aiModel });
}Override flags in tests, Storybook, and development:
import { FlagsTestProvider } from "@/components/flags/flags-test-provider";
// In Storybook stories
export const DarkMode = () => (
<FlagsTestProvovider
overrides={{
"my-awesome-feature": true,
"theme-mode": "dark"
}}
>
<MyComponent />
</FlagsTestProvider>
);
// In Jest tests
test("premium features", () => {
render(
<FlagsTestProvider overrides={{ "premium-features": true }}>
<App />
</FlagsTestProvider>
);
expect(screen.getByText("Premium Feature")).toBeInTheDocument();
});- Server-side resolution: All decision logic runs securely on the server
- Client filtering: Only
client.public: trueflags are sent to browsers - Parallel execution: All flags resolve simultaneously for optimal performance
- Type safety: Full TypeScript support with runtime Zod validation
- Context isolation: User/workspace data stays on the server
- Zero build step: No codegen or compilation required
- SSR optimized: Flags resolve during server-side rendering
- Edge compatible: Works with Vercel Edge Runtime (with limitations)
- CI/CD integration: Consistency checker prevents configuration drift
- Caching friendly: Use React
cache()for expensive context operations
- Live Demo - Interactive feature flags showcase
- Documentation - Complete setup and usage guide
- Vercel Flags SDK - The underlying foundation we build upon
Contributions are welcome! Please open issues or pull requests on GitHub. For larger changes, file an issue first to discuss scope and approach.
MIT β see LICENSE in the repository.