Skip to main content

Philosophy

FlagKit treats feature flags as first-class TypeScript constructs rather than runtime configuration. This means your flags are:
  • Type-checked at compile time
  • Version-controlled with Git
  • Reviewed through pull requests
  • Deployed with your application code

Traditional vs. FlagKit Approach

// Runtime string literals - prone to typos
const isEnabled = featureFlags.get('new_checkout'); // Typo: should be 'newCheckout'

// No type safety
const value: any = featureFlags.get('theme'); // Could be anything!

// No autocomplete
featureFlags.get('...') // What flags exist?

Defining Flags

Flags are defined in flags.config.ts using the FlagKit schema:
flags.config.ts
import { defineFlags, flag } from "@flagkit-io/core";

export const flags = defineFlags({
  // Boolean flag
  newCheckout: flag
    .boolean()
    .description("Enable new checkout flow")
    .default(false)
    .tags(["payments", "ui"]),

  // String flag with validation
  apiEndpoint: flag
    .string()
    .description("API base URL")
    .default("https://api.production.com")
    .pattern(/^https:\/\//), // Must be HTTPS

  // Number flag with constraints
  maxRetries: flag
    .number()
    .description("Maximum retry attempts")
    .min(0)
    .max(10)
    .default(3),

  // Enum flag
  theme: flag
    .enum(["light", "dark", "auto"])
    .description("UI theme")
    .default("auto"),
});

Code Generation

Running flagkit generate produces:
export type FlagName = 'newCheckout' | 'apiEndpoint' | 'maxRetries' | 'theme';

export type FlagValues = {
  newCheckout: boolean;
  apiEndpoint: string;
  maxRetries: number;
  theme: 'light' | 'dark' | 'auto';
};

export type FlagContext = {
  userId?: string;
  rolloutPercent?: number;
  environment?: string;
  [key: string]: any;
};

Benefits

Type Safety

Catch errors at compile time, not runtime

Autocomplete

IDE knows all available flags and their types

Refactoring

Rename flags safely across your codebase

Documentation

Flags self-document with descriptions and types

Git Workflow

Because flags are code, they follow your normal Git workflow:
# 1. Create feature branch
git checkout -b feature/new-checkout

# 2. Add flag
# Edit flags.config.ts

# 3. Generate types
flagkit generate

# 4. Implement feature using flag
# Edit your application code

# 5. Commit everything together
git add flags.config.ts src/
git commit -m "feat: add new checkout with flag"

# 6. Open PR for review
git push origin feature/new-checkout

Version Control

Flag definitions are committed to Git, but generated code typically is not:
.gitignore
# Don't commit generated code (regenerated on each build)
.flagkit/generated/

# Do commit flag definitions
# flags.config.ts
In CI/CD, run flagkit generate as part of your build process to ensure generated code is always fresh.

Schema Validation

FlagKit validates your flag definitions at generation time:
flags.config.ts
export const flags = defineFlags({
  // ❌ Error: Invalid default value
  theme: flag.enum(["light", "dark"]).default("auto"), // 'auto' not in enum!

  // ❌ Error: Invalid number range
  pageSize: flag
    .number()
    .min(10)
    .max(5) // Max can't be less than min!
    .default(20),

  // ❌ Error: Default doesn't match pattern
  email: flag
    .string()
    .pattern(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/)
    .default("not-an-email"),
});

Next Steps

Local Evaluation

Learn how FlagKit evaluates flags without API calls