TL;DR
npx shadcn@latest add https://flags.griffen.codes/r/flags-core and .../flags-cli.Feature flags have become an indespensible part of my development workflow in recent years. Specifically, I’ve been using Vercel’s Flags SDK on a multiple personal and professional projects and I think it’s great! 👏 But alas, I have fallen victim to my insufferable urge to automate & simplify my interaction with technology at every opportunity.
And so, as a result I built Fargo Flags: a streamlined toolkit that enhances the Flags SDK with a small suite of core components and CLI tools to assist managing projects with an ever-expanding number of features… and flags.
Most feature flag services follow the same pattern:
Vercel’s Flags SDK took a different approach with “flags as code” – keeping flag logic in your codebase where it belongs. But while the architecture was solid, the developer experience had room for improvement:
Fargo Flags builds on the Flags SDK’s foundation while addressing these pain points:
Each feature flag lives in its own file with complete type safety:
// src/lib/flags/defs/enable-ai-assistant.flag.ts
import { z } from "zod";
import { defineFlag } from "../kit";
export const key = "enable-ai-assistant-in-pdf-toolbar" as const;
export const schema = z.boolean();
export default defineFlag({
  key,
  schema,
  description: "Enable AI assistant in PDF toolbar",
  defaultValue: false,
  client: { public: true }, // Expose to client
  async decide(ctx) {
    const user = await ctx.getUser?.();
    return user?.plan === "premium";
  },
});Creating flags becomes effortless with the interactive wizard:
$ pnpm flags:new
✔ Flag key (kebab-case) … enable-premium-features
✔ Value type › boolean
✔ Expose to client? … yes
✔ Default value … false
✔ Description … Enable premium features for paid users
✔ created src/lib/flags/defs/enable-premium-features.flag.ts
✔ updated src/lib/flags/registry.config.tsThe wizard handles all the boilerplate:
Or run the consistency checker anytime:
pnpm flags:checkThis validates the registry completeness and client exposure, perfect for CI.
Flag Definitions (one file per flag)
        │
        ▼
Checked-in Registry (registry.config.ts)  ← auto-updated by CLI
        │
        ▼
Server Resolution (resolveAllFlags)
        │
        ▼
Client Subset (pickClientFlags) → <FlagsProvider>
        │
        ├── useFlag('key')
        └── <Flag when="key" />
Flags resolve on the server for security and performance, then hydrate client-safe values:
// app/layout.tsx
export default async function RootLayout({ children }) {
  // Resolve ALL flags on server (including sensitive ones)
  const serverFlags = await resolveAllFlags({
    getUser: async () => getCurrentUser(),
    getWorkspace: async () => getCurrentWorkspace(),
  });
  
  // Extract only client-safe flags
  const clientFlags = pickClientFlags(serverFlags);
  return (
    <html>
      <body>
        <FlagsProvider flags={clientFlags}>
          {children}
        </FlagsProvider>
      </body>
    </html>
  );
}Clean, declarative flag usage in components:
import { useFlag } from "@/components/flags/flags-provider";
import { Flag } from "@/components/flags/flag";
function Dashboard() {
  const isPremium = useFlag("enable-premium-features");
  
  return (
    <div>
      <h1>Dashboard</h1>
      
      {/* Hook-based usage */}
      {isPremium && <PremiumFeatures />}
      
      {/* Declarative component */}
      <Flag when="enable-analytics">
        <AnalyticsPanel />
      </Flag>
      
      {/* With fallback */}
      <Flag 
        when="enable-premium-features" 
        fallback={<UpgradePrompt />}
      >
        <PremiumFeatures />
      </Flag>
    </div>
  );
}
Override flags easily in tests and Storybook:
import { FlagsTestProvider } from "@/components/flags/flags-test-provider";
// Unit tests
test("shows premium features when enabled", () => {
  render(
    <FlagsTestProvider overrides={{ "enable-premium-features": true }}>
      <Dashboard />
    </FlagsTestProvider>
  );
  
  expect(screen.getByText("Premium Features")).toBeInTheDocument();
});
// Storybook stories
export const PremiumUser = {
  decorators: [
    (Story) => (
      <FlagsTestProvider 
        overrides={{ 
          "enable-premium-features": true,
          "theme-mode": "dark"
        }}
      >
        <Story />
      </FlagsTestProvider>
    ),
  ],
};
Install via familiar shadcn/ui-style commands:
# Core system
npx shadcn@latest add https://flags.griffen.codes/r/flags-core
# Optional components
npx shadcn@latest add https://flags.griffen.codes/r/flags-flag
npx shadcn@latest add https://flags.griffen.codes/r/flags-test-provider
# CLI tools
npx shadcn@latest add https://flags.griffen.codes/r/flags-cli
Validate flag consistency in your pipeline:
$ pnpm flags:check
✔ flags:check OK — 4 registered, 4 files, 3 client-exposed
# Or catch issues early:
Defs present but missing in registry.config:
  - new-experimental-flag
Public flags in files but missing from clientFlagKeys:
  - enable-ai-assistant-in-pdf-toolbar
The heart of the system is resolveAllFlags() – a server-side engine that:
defaultValue when decide() functions aren’t providedexport 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;
}
pickClientFlags() respects client.public settingsserialize() functions sanitize client valuesUnlike code generation approaches, Fargo Flags uses static imports with a checked-in aggregator. The CLI wizard programmatically updates this file using special comment tags, which is a simple and robust way to manage updates to a source file.
// registry.config.ts - maintained by CLI wizard
// @fargo-flags:imports
import * as f_enable_ai_assistant from "./defs/enable_ai_assistant.flag";
import * as f_theme_mode from "./defs/theme_mode.flag";
// @fargo-flags:imports:end
export const registry = {
  // @fargo-flags:registry
  "enable-ai-assistant-in-pdf-toolbar": f_enable_ai_assistant.default,
  "theme-mode": f_theme_mode.default,
  // @fargo-flags:registry:end
} as const;
This “set and forget” approach provides several advantages:
export default defineFlag({
  key: "new-checkout-flow",
  schema: z.boolean(),
  defaultValue: false,
  client: { public: true },
  async decide(ctx) {
    const user = await ctx.getUser?.();
    
    // Gradual rollout based on user ID
    const hash = hashUserId(user?.id);
    return hash % 100 < 25; // 25% of users
  },
});export default defineFlag({
  key: "pricing-page-variant",
  schema: z.enum(["control", "variant-a", "variant-b"]),
  defaultValue: "control",
  client: { public: true },
  async decide(ctx) {
    const user = await ctx.getUser?.();
    const hash = hashUserId(user?.id);
    
    if (hash % 3 === 0) return "variant-a";
    if (hash % 3 === 1) return "variant-b";
    return "control";
  },
});
export default defineFlag({
  key: "ai-model-selection",
  schema: z.enum(["gpt-4o-mini", "gpt-4", "claude-3-sonnet"]),
  defaultValue: "gpt-4o-mini",
  client: { public: false }, // Server-only
  async decide(ctx) {
    const workspace = await ctx.getWorkspace?.();
    
    if (process.env.NODE_ENV === "development") {
      return "gpt-4o-mini"; // Cheaper for dev
    }
    
    return workspace?.plan === "enterprise" 
      ? "claude-3-sonnet" 
      : "gpt-4";
  },
});
Every decision prioritizes developer productivity:
Built for real applications:
Try Fargo Flags in your Next.js project:
# Install core system
npx shadcn@latest add https://flags.griffen.codes/r/flags-core
# Install CLI tools
npx shadcn@latest add https://flags.griffen.codes/r/flags-cli
# Add package.json scripts
{
  "scripts": {
    "flags:new": "tsx scripts/create-flag.ts",
    "flags:check": "tsx scripts/check-flags-registry.ts"
  }
}
# Create your first flag
pnpm flags:new
Fargo Flags represents a new approach to feature flags – one that embraces the “flags as code” philosophy while providing the tooling to save developers time and provide
The project is open source and actively maintained. I’m excited to see how teams adopt it and what improvements the community suggests.
Try it out and let me know what you think! The full documentation and examples are available at flags.griffen.codes.
Fargo Flags is built on top of Vercel’s Flags SDK and follows their excellent architectural patterns. Special thanks to the Vercel team for their work 👏