Skip to content

A thin layer of abstraction that enhances Vercel's Flags SDK with streamlined tooling and distribution. Embracing the same core principles while making them easier to adopt and scale.

License

Notifications You must be signed in to change notification settings

gfargo/fargo-flags

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

37 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Fargo Flags ⛳️

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.

🀝 Built on Vercel's Flags SDK (Now with Batteries included βš‘οΈπŸ”‹)

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.

🧭 Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     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.ts files to src/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 via useFlag('key') or <Flag when="key" />.

πŸš€ Quick Start

1. Install Core System

npx shadcn@latest add https://flags.griffen.codes/r/flags-core

This installs the core flag system including types, runtime, and React provider.

2. Install CLI Tools

npx shadcn@latest add https://flags.griffen.codes/r/flags-cli

Add these scripts to your package.json:

{
  "scripts": {
    "flags:new": "tsx scripts/create-flag.ts",
    "flags:check": "tsx scripts/check-flags-registry.ts"
  }
}

3. Set Up Your App

// 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>
  );
}

4. Create Your First Flag

pnpm flags:new

Follow the interactive wizard to create a type-safe feature flag with automatic registry updates.

5. Use in Components

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>
  );
}

πŸ“¦ Registry Components

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

⚠️ Limitations & Gotchas

  • 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 defaultValue that conforms to it.

❓ FAQ

  • 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.

πŸ“ Project Structure

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

πŸ› οΈ CLI Tools

The CLI tools streamline flag management and ensure consistency:

Interactive Flag Creation

pnpm flags:new
  • Guided prompts for flag configuration
  • Automatic TypeScript generation
  • Registry updates with proper imports
  • Code formatting with Prettier

Consistency Validation

pnpm flags:check
  • Validates registry completeness
  • Checks client flag consistency
  • Perfect for CI/CD pipelines
  • Catches configuration drift early

Available Scripts

  • pnpm flags:new - Create a new feature flag
  • pnpm flags:check - Validate flag registry consistency
  • pnpm dev - Start development server
  • pnpm build - Build for production

πŸ“– Documentation

Defining Flags

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";
  },
});

Flag Types

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";
  },
});

Using Flags

Client-Side with Hooks

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>
  );
}

Conditional Rendering with Components

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>
  );
}

Server-Side Usage

// 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 });
}

Testing & Development

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();
});

πŸ”’ Security & Performance

  • Server-side resolution: All decision logic runs securely on the server
  • Client filtering: Only client.public: true flags 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

πŸš€ Production Ready

  • 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

πŸ”— Links

🀝 Contributing

Contributions are welcome! Please open issues or pull requests on GitHub. For larger changes, file an issue first to discuss scope and approach.

πŸ“„ Licensing

MIT β€” see LICENSE in the repository.

About

A thin layer of abstraction that enhances Vercel's Flags SDK with streamlined tooling and distribution. Embracing the same core principles while making them easier to adopt and scale.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •