- Docs
- Operate
- Feature flags
Feature flags
Gate code paths with typed flags from a static file or Firebase Remote Config.
flag is a typed on/off switch that is safe in a hot request handler. The interface exposes Bool, String, Int, and JSON lookups against per-request targeting context from WithEvalContext. A missing key returns your fallback with a nil error, so a flag lookup can never take the app down.
Quick start
Section titled “Quick start”static (the default) loads a JSON file once and costs no network call per evaluation. Use it for tests, offline dev, and rollouts that can ship with a deploy.
FLAG_ADAPTER=staticFLAG_STATIC_PATH=data/flags.jsonFLAG_STATIC_RELOAD=falseimport ( "context" "github.com/zenta-dev/zever/adapters/flag/static" "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container" "github.com/zenta-dev/zever/core/flag")
static.Register() // container wires nothingc := container.New(config.Default())f, err := c.Flag()if err != nil { panic(err)}defer f.Close()
ctx := flag.WithEvalContext(context.Background(), flag.EvalContext{ RandomizationID: "user-123", Signals: map[string]any{"plan": "pro"},})on, err := f.Bool(ctx, "new_checkout", false)fmt.Println(on, err)Expected output (empty static set, so the fallback applies):
false <nil>An empty static path is valid and means an empty flag set. Because the fallback is false, a missing key or an outage keeps serving the old checkout.
Flip flags without a deploy
Section titled “Flip flags without a deploy”Use firebase when product or growth must change a flag live. It adds a real dependency and a 30s default operation timeout (DefaultTimeout). Durations in files are integer nanoseconds.
FLAG_ADAPTER=firebaseFLAG_FIREBASE_PROJECT_ID=my-projectFLAG_FIREBASE_SERVICE_ACCOUNT=/run/secrets/firebase.jsonFLAG_FIREBASE_TIMEOUT=30sFLAG_ADAPTER=firebaseFLAG_FIREBASE_PROJECTID=my-projectFLAG_FIREBASE_TIMEOUT=30sValidate rejects only a negative firebase.timeout; zero means the default. Adapters are static (adapters/flag/static, the default) and firebase (adapters/flag/firebase, Remote Config; needs project_id and service_account). See the flag config reference.
Production checklist
Section titled “Production checklist”Flag.Close() errortakes no context. The container probes it duringClose(ctx), joins errors witherrors.Join, and surfaces panics as*ClosePanicError.staticis per-process, so a file change only lands where the file lands (plusreloadwhen enabled).firebaseis the shared source of truth, so design fallbacks so every replica degrades the same way when it is unreachable.- Never log raw option maps (use
cfg.RedactedServices()). The service account is a file path, not key material, but keep it out of logs.
Troubleshooting
Section titled “Troubleshooting”A flag always returns the fallback
Check that the static file path resolves relative to the working directory (an empty or missing path means an empty set by design), and that the key spelling matches. A hierarchy such as billing.checkout_v2 avoids collisions.
Type mismatch error on an existing key
The stored JSON type does not match the accessor (String on a bool key returns the fallback plus an error). Align the file value with the call, or read it via JSON.
Firebase open fails or times out
Verify project_id, the service_account JSON path, and egress to the Remote Config endpoint. Only a negative timeout fails Validate, so a hang is a network problem, not a config shape problem.