Feature flags and remote config
config keeps flags and configs per project environment. Apps read them with an
environment's delivery key and follow changes over Server-Sent Events.
Flags
A flag is boolean, string, number or JSON. Per environment it is:
- on: rules are tried in order, then the fallthrough value is served;
- off: everyone gets its off value. That is the kill switch.
Rules compare an attribute of the evaluation context: in, not_in, contains, starts_with,
ends_with, lt, gt, version_gte, version_lt. Any served value can be a percentage rollout,
sticky by an attribute (the user id, say).
xvii config flags --project shop
xvii config set new-checkout on --env preview --project shop
xvii config set banner 'Sale until Monday' --env production --project shop
xvii config set new-checkout off --env production # kill switch
Configs
JSON documents per environment, checked against an optional JSON Schema, versioned with diffs and
rollback. Use them for limits, copy, lists that change more often than code.
Read them in an app
Each environment has a delivery key cfg_…: read-only, safe to ship to browsers, rotatable. Keep values
non-secret; secrets belong in env.
c, err := sdk.New(ctx, "https://config.xvii.network", os.Getenv("XVII_CONFIG_KEY"))
if c.Bool("new-checkout", sdk.Context{"id": userID}, false) {
// …
}
The SDK (go.xvii.network/config/sdk) evaluates locally and follows the stream.
// https://config.xvii.network/sdk/xvii-config.ts, copied into the project
import { XviiConfig } from "./xvii-config"
const config = await XviiConfig.connect("https://config.xvii.network", process.env.XVII_CONFIG_KEY!)
if (config.bool("new-checkout", { id: user.id, plan: user.plan }, false)) {
// …
}
One file, no dependencies; Node 18+, Bun, Deno and browsers. It evaluates locally the way the server
does and follows changes.
curl -H "Authorization: Bearer $XVII_CONFIG_KEY" https://config.xvii.network/v1/flags
curl -H "Authorization: Bearer $XVII_CONFIG_KEY" https://config.xvii.network/v1/configs/limits
curl -N -H "Authorization: Bearer $XVII_CONFIG_KEY" https://config.xvii.network/v1/stream
GET /v1/ruleset (with an ETag) returns everything for local evaluation; GET|POST /v1/flags evaluates
for a context on the server.
Use them safely
- Cache the last good ruleset and use a short timeout: a config outage must not slow every request.
- Keep health checks available during maintenance switches, or a deploy rolls back because the switch
returns 503.
- Test destructive switches in preview first, and write down each flag's default and failure behaviour.
- Let config do the bucketing; don't write a second algorithm in the app.
Changes are made by a team's developers; protected environments need an admin. Limits config.flags,
config.configs.