Feature flags
On this page
A feature flag lets you ship code switched off, then turn it on for some
users, a share of users, or everyone, without a deploy. You declare every
flag once, in the file flags.jadpo at the root of the project, with its
type, its default, its owner and the date it expires. The /// comment
above a flag is its description, as for every declaration.
// flags.jadpoflags { /// The one-page checkout replacing the three-step form. new_checkout: Bool { default: false owner: "payments" expires: "2027-01-31" } /// Which layout the checkout uses while we compare two. checkout_layout: CheckoutLayout { default: CheckoutLayout.classic owner: "payments" expires: "2026-12-15" }}
A flag is a Bool, or an enum when it has more than two variants. Code
reads it as flags.new_checkout, in actions and in a route's run:
arguments. Functions and queries can't read a flag, so you pass them its
value as a parameter. As with clock.now, the value is fixed for the
whole operation, so one request never sees a flag change halfway through.
// domains/ordering/actions/place_order.jadpoaction place_order(user_id: User.id, input: PlaceOrder) -> OrderView { if (flags.new_checkout) { return place_order_in_one_step(user_id, input) } ...}
Changing a flag
A flag's current state lives in your app's own database, in a table Jadpo
owns. You change it while the app runs, with jadpo flags against an
environment
(Commands for services, calls, email and stored data).
Each change is logged with who made it.
Each server holds the flag values in its own memory and refreshes them every few seconds, so a change applies within ten seconds. Flags are never held in the cache. If the database can't be read, each server keeps the last values it had, or the defaults, so reading a flag never fails a request.
Flags aren't a capability, and no outside flag service fills them.
jadpo flags set new_checkout --env production --users 0192... --percent 10jadpo flags set checkout_layout --env production --split classic=50,compact=50jadpo flags list --env production
Who gets which value
The first rule that matches wins:
- Named users get a fixed value.
- Named tenants get a fixed value. This rule comes once tenancy is settled for flags (Tenancy).
- A percentage of users, chosen by a stable hash of the flag's name and
the user's
id. The same user always gets the same answer, raising 10% to 20% keeps the first 10% in, and each flag picks a different 10%. - Everyone else gets the flag's current value, at first its
default.
The runtime knows the caller, as it does for policy, so you don't pass it. Schedules and subscribers have no caller (Background work), so they get the everyone value. An action that needs a user's variant later puts the value in the event it emits.
What the compiler checks
flags.new_chekoutis a compile error, like any unknown name.- Every flag read needs a test for each value. A test chooses the value
with
flags: { new_checkout: true }, besideclock:andconfig:(Test settings).jadpo checkfails until the tests that reach each read cover bothtrueandfalse, or every variant. - A flag past its
expires:date is an error naming its owner, and you can't decline it.jadpo checkjudges the date against today, andjadpo buildagainst the commit's date, so rebuilding a merged revision is never blocked by a date passing. To keep a flag longer you edit the date, so keeping one is a reviewed choice. - Whether a flag has sat at 100% or 0% is runtime state the compiler
can't see, so
jadpo flags listmarks those flags "ready to remove".
Removing a flag
jadpo flags remove new_checkout --keep true rewrites your source: each
read becomes true, the branch that can no longer run goes, the
declaration goes, and test overrides for the removed value are deleted.
Jadpo can do this exactly because it knows every read.
Flags in browsers and apps
A flag with client: true is also returned, worked out for the caller,
by a generated GET /flags route in the OpenAPI document.
What flags are not for
Flags are for releasing code. What a customer's plan includes is your
app's data, guarded by policy. A setting that differs between
environments is configuration
(Settings: config.jadpo).
Markdown for agents: /docs/feature-flags.md.Built from Jadpo main at a707efb.