v1.0.0 · in development

Feature flags
built for great DX

Feature flags as code. Strongly typed, Git-friendly, and built to eliminate string-based errors.

bash

Features

EVERYTHING THE TYPE SYSTEM NEEDS

Six primitives that cover the whole lifecycle from definition to rollout

01Schema-first

One file. Total control.

Define your flags in TypeScript. VoidFlag derives every type, every fallback, every constraint from that one file — nothing lives in a dashboard you can't version.

TypeScript
// voidflag.schema.ts
export const schema = defineFlags({
  darkMode: boolean().fallback(false),
  theme:    string().fallback('light'),
  fontSize: number().fallback(16),
});
02CLI workflow

Schema → generate → push → apply.

The entire lifecycle runs through vf. Generate a typed state file, push your schema to the server, then apply it to any environment. No UI required.

TypeScript
# scaffold schema + config
vf init

# generate voidflag.state.ts from schema
vf generate

# sync schema with the server
vf push

# apply state to an environment
vf apply --env staging
03Three ways to read

Pick the right accessor for the job.

get() resolves the value once. flag() returns a live accessor that always reflects the current store. flags.x is shorthand for flag(). same live accessor with dot access. accessors are created lazily and cached, so no extra allocations.

TypeScript
// get() — resolved value, one shot
const value = client.get('darkMode');
//    ^? boolean

// flag() — live accessor object
const f = client.flag('theme');
f.value;    // enabled ? value : fallback
f.fallback; // 'light'
f.enabled;  // true
f.rollout;  // 100

// flags.x — lazy shorthand for flag()
client.flags.fontSize.value;
client.flags.darkMode.enabled;
04Fully typed

The compiler knows your flags.

Every accessor is typed to its exact primitive — no casting, no any. Typos and wrong types are compile errors before they ship.

TypeScript
client.get('darkMode');  // boolean
client.get('theme');     // string
client.get('fontSize');  // number

// Typos don't compile.
client.get('darkMood');
//          ~~~~~~~~~
// Argument of type '"darkMood"' is not
// assignable to parameter of type 'keyof S'.
05DI-ready

Flags as injection decision points.

Use flag state to resolve which implementation gets injected at runtime. Swap strategies, migrate dependencies, or A/B test entire service layers — no redeploy, just flip the flag.

TypeScript
// swap payment provider at runtime
const paymentService = client.flags.useStripe.value
  ? new StripeService()
  : new PayPalService();

container.register(PaymentService, paymentService);

// gradual migration — stable per user
const service = client.isRolledOutFor('useStripe', user.id)
  ? new StripeService()
  : new PayPalService();
06Stable rollouts

Gradual. Deterministic. Reversible.

Roll out to a percentage of your users with a stable hash — same user, same result, every time. Dial up, dial back, or kill-switch from the state file.

TypeScript
// 20% rollout — deterministic per user
client.isRolledOutFor('darkMode', user.id);
// user_42 → bucket 17 → true  ✓
// user_99 → bucket 83 → false

// allEnabled() — gate on multiple flags at once
client.allEnabled(['darkMode', 'newCheckout']);
// false if any flag is disabled

live demo

Select a flag · tune its props · watch users resolve to value or fallback

A/B test: which hero copy does this user see?

"control" | "challenger"
fallbackcontrol
enabled
Flag is live — rollout decides who gets value vs fallback
value
What users inside rollout receive
rollout
% of users that receive value · rest get fallback
50%
user_42b72fallback control
user_99b30value challenger
user_07b1value challenger
user_88b62fallback control
user_42fallback
✦ beta
Feature flags for TS.
Schema-first.
✓ Express checkout
user_99value
✦ beta
New
Ship flags faster.
✓ Express checkout
user_07value
New
Ship flags faster.
Legacy checkout
user_88fallback
✦ beta
Feature flags for TS.
Schema-first.
✓ Express checkout
But.., Why VoidFlag?

STOP SHIPPING FLAGS YOU CAN'T TRUST.

Runtime flags are silent, untyped, and untraceable. VoidFlag makes them compile-time citizens.

Before
Fragile
if (flags.isEnabled("new-chekout") {
checkout.render()
}
  • Typo compiles fine, fails at runtime
  • Undefined flags return false silently
  • No types for values or variants
  • Changes in audit logs, not Git
After
Type-Safe
if (flags.newCheckout.enabled) {
checkout.render()
}
  • Unknown flags won't compile
  • Schema-defined, build-time types
  • Strict values and metadata
  • Versioned state reviewed in PRs
Ecosystem

Built for the TypeScript ecosystem.

Backend-first today. Expanding to more runtimes soon.

SupportedREADY
  • Node.js

    Node.js

    Full runtime support

  • Express

    Express

    Works inside route handlers

  • NestJS

    NestJS

    Module integration

  • Next.js

    Next.js

    Server-side & Edge

Coming SoonIn Progress
  • BunBun
  • DenoDeno
  • .NET.NET
  • GoGo
  • JavaJava
  • PythonPython
Philosophy

FLAGS TREATED LIKE CODE

Typed at build time. Versioned in Git. Controlled without redeploys.

Schema-first

Flags live in a schema file inside your repo — not in a dashboard. They exist at build time and generate types automatically.

Compile-time safety

Undefined flags don't silently return false. They throw at compile time. Misuse is caught before it reaches production.

Git as source of truth

State changes are versioned, reviewed in PRs, and traceable. No more hunting through audit logs.

Think Prisma-style schema discipline applied to feature flags. VoidFlag is built for teams who want strictness, not chaos.

EARLY ACCESS

join the waitlist

be first when voidflag ships

no spam · unsubscribe anytime