Feature flags
Built-in feature flags with targeting rules, percentage rollout, multivariate variations, and an OpenFeature-aligned eval API.
Vaultbase ships a real feature flag system — not a glorified settings table.
Boolean / string / number / JSON flags, ordered targeting rules, percentage
rollouts with sticky bucketing, and an admin UI at /_/flags.
Data model
Section titled “Data model”CREATE TABLE vaultbase_feature_flags ( key TEXT PRIMARY KEY, description TEXT NOT NULL DEFAULT '', type TEXT NOT NULL DEFAULT 'bool', -- bool | string | number | json enabled INTEGER NOT NULL DEFAULT 1, -- master kill switch default_value TEXT NOT NULL DEFAULT 'false',-- JSON-encoded scalar variations TEXT NOT NULL DEFAULT '[]', -- JSON: named variations rules TEXT NOT NULL DEFAULT '[]', -- JSON: ordered targeting rules created_at, updated_at INTEGER);A flag’s effective value is computed by walking rules top-to-bottom and
returning the first matching rule’s variation. No match → default_value.
enabled = 0 short-circuits to default — the kill switch.
Rule shape
Section titled “Rule shape”{ "id": "9b4c1a2", "when": { "all": [ { "attr": "user.plan", "op": "in", "value": ["pro", "enterprise"] }, { "attr": "user.email", "op": "ends_with", "value": "@acme.com" } ] }, "rollout": { "value": 25, "sticky": "user.id" }, "variation": "treatment_a"}when is a boolean tree (all / any / not / segment / leaf).
{ "segment": "<name>" } matches a saved segment’s conditions (segments are
managed under /api/v1/admin/flag-segments, or with Segments on the admin’s
flags page). The admin edits a rule’s conditions visually when they’re a single
all list of checks, and as JSON (with its prerequisites) otherwise. Operators:
| Op | Notes |
|---|---|
eq neq | Strict equality. |
in not_in | value must be an array. |
contains starts_with ends_with | String only. |
gt gte lt lte | Number only. |
between | value: [min, max] inclusive. |
exists | Attribute is present and not null (ignores value); false, 0 and "" count as present. |
regex | value is a regex string. Anchors not implicit. |
Attributes are dotted paths into the evaluation context (user.plan,
request.country, feature_flags.beta_optin).
Percentage rollout
Section titled “Percentage rollout”rollout.value is 0-100. The hash of flag_key + ":" + context[rollout.sticky]
mod 100 must be < the rollout value for the rule to fire. Deterministic
— same user lands in the same bucket across processes — and sticky —
once you’re in, you stay in even if other rules change.
A losing roll causes the engine to fall through to the next rule, so a follow-up rule with no rollout can still pick up the long tail.
Prerequisites
Section titled “Prerequisites”A rule can require other flags first:
{ "id": "p1", "prerequisites": [{ "flag": "checkout_variant", "variation": "treatment_a" }], "variation": "true" }The rule fires only when every listed flag evaluates to that variation (or value) for the same context; otherwise the engine moves to the next rule. Prerequisites nest up to 8 levels.
Server-side evaluation (hooks / routes / cron)
Section titled “Server-side evaluation (hooks / routes / cron)”// inside a hookconst enabled = await ctx.helpers.flags.isEnabled("new_checkout", { auth: ctx.auth,});
const variant = await ctx.helpers.flags.getString("checkout_variant", "control", { auth: ctx.auth,});
const cfg = await ctx.helpers.flags.getJson<{ brand: string }>("payment_cfg", { brand: "stripe" }, { auth: ctx.auth,});The helpers use the context exactly as you pass it. ctx.auth is the
verified caller; passed as auth, flag rules can target auth.id,
auth.type, auth.email, auth.collection, etc., the same attributes the
public eval API sets from the session.
Public bulk eval API
Section titled “Public bulk eval API”For client SDKs and external services:
POST /api/v1/flags/evaluate{ "context": { "user": { "id": "u1", "plan": "pro" } }, "keys": ["new_checkout", "checkout_variant"] // optional — omit for all} → { "data": { "new_checkout": true, "checkout_variant": "treatment_a" } }No auth required by default (flags are presentation-only — the targeting context the caller sends is what determines their treatment). Don’t put secrets in flag values.
The auth attribute is the exception: the server sets it from the caller’s
session — auth.id, auth.type, auth.email, auth.collection,
auth.anonymous — and drops any auth the caller sent, so rules that must
not be spoofed should target auth.*. Set flags.public_evaluate to 0 to
answer 401 to callers without a session.
Admin endpoints
Section titled “Admin endpoints”GET /api/v1/admin/flags — listGET /api/v1/admin/flags/:key — read onePOST /api/v1/admin/flags — createPATCH /api/v1/admin/flags/:key — update (partial)DELETE /api/v1/admin/flags/:key — deletePOST /api/v1/admin/flags/:key/evaluate — test-context previewAll require admin auth and emit audit-log rows under flags.* actions.
Admin UI
Section titled “Admin UI”/_/flags shows a two-pane editor:
- Left — list of flags. Color dot for enabled state, type + rule count in the meta line.
- Right — full editor: kill switch, key + type, description, default
value, variations (multivariate flags), targeting rules with conditions
- rollout sliders.
- Test context panel — paste JSON, click Evaluate, see the trace:
resolved value, variation, reason (
rule_match,disabled,no_match,missing), and which rule id matched.
Standards alignment
Section titled “Standards alignment”The eval surface mirrors the OpenFeature provider spec:
- Variations are named buckets, not anonymous values.
- Evaluation context is a freeform object, not a fixed user shape.
evaluate()returns value + reason + variation, not just the value (so callers can log telemetry on flag decisions).
The SDK ships an OpenFeature provider, VaultbaseFlagsProvider from
@vaultbase/sdk/openfeature (see SDK feature flags).
Limits & not-in-scope
Section titled “Limits & not-in-scope”- Single-process cache — flags refresh from DB every 5 s. A fleet of
vaultbase instances all converge within that window. Admin changes are
also pushed on the
__flagsrealtime topic (flag_changed/flag_deleted), which the SDK uses to re-evaluate. - No experimentation — flag values are reported, but conversion metrics aren’t bound to them. Phase 3.
- Targeting context is caller-supplied — vaultbase doesn’t infer it,
except
auth.*on the public eval API, which comes from the session. In hooks,ctx.authis canonical.