Traditional feature flag systems use string literals and untyped values:
Copy
// ❌ Problems with traditional approachconst isEnabled = flags.get("new_checkout"); // Typo in flag nameconst theme = flags.get("theme"); // What type is this?const maxItems = flags.get("maxItems"); // Could be undefined// Runtime errors waiting to happen!if (theme.toUpperCase() === "DARK") { // TypeError if theme is undefined // ...}
// ❌ Compile errorconst value = flags.get("newChckout");// ~~~~~~~~~~~~// Error: Argument of type '"newChckout"' is not assignable// to parameter of type 'FlagName'
// ❌ Compile errorexport const flags = defineFlags({ theme: flag.enum(["light", "dark"]).default("auto"), // 'auto' not in enum! // ~~~~~~ // Error: Argument of type '"auto"' is not assignable to // parameter of type '"light" | "dark"'});
Rename flags confidently using TypeScript’s refactoring tools:
Copy
// Before: Flag named 'newCheckout'const isEnabled = flags.get("newCheckout");// After: Rename to 'newCheckoutFlow'// 1. Rename in flags.config.ts// 2. Run `flagkit generate`// 3. TypeScript shows all places that need updatingconst isEnabled = flags.get("newCheckout");// ~~~~~~~~~~~~// Error: Argument of type '"newCheckout"' is not assignable...
Use your IDE’s “Find All References” feature to locate all usages of a flag
before renaming.
// Runtime validationconst pageSize = flags.get("pageSize"); // number (1-100)// TypeScript can't enforce numeric ranges, but runtime will// If someone edits decision-tree.json to set pageSize: 200,// FlagKit will clamp to 100
TypeScript can enforce type (string, number, boolean) but not value ranges.
Runtime validation ensures values stay within constraints.