Skip to content

Toggle a feature flag

Store feature flags as JSON in Workers KV, let any visitor read them, and let only an authenticated editor create, change, or delete them.

commons keys
---
A flag name: 1–64 characters. Used as part of the KV key, so it is validated at
the boundary before any handler runs. A refined `String`, so it projects
structurally across the context boundary — the HTTP service mixes in this same
constraint for its path parameters.
---
type FlagKey = String where MinLength(1) && MaxLength(64)
---
The KV key for a flag. Flags share a KV namespace with anything else, so they are
key-prefixed.
---
fn keyOf(name: FlagKey) -> String {
"flag:\(name)"
}
---
Strip the `flag:` prefix (5 characters) back to the bare name — the inverse of
`keyOf`, used when listing.
---
fn nameOf(key: String) -> String {
key.slice(5, key.length())
}
Open the full project ↗

This example reaches Workers-only shapes (storage bindings, agents, or cron), so it runs with bynk dev rather than in the browser playground. See Install to get started.

The flag name is a refined type — FlagKey = String where MinLength(1) and MaxLength(64) — defined in commons keys alongside the key helpers keyOf (which namespaces a name as flag:…) and nameOf (its inverse, stripping the five-character prefix). Because the constraint lives in a commons, it is type-checked and unit-tested without a KV binding, and the context flags mixes the same type into its path parameters.

The context flags consumes bynk.cloudflare { Kv }, which locks the unit to Cloudflare. Two actors sit over one service: User is a Bearer actor whose identity is a non-empty UserId, and Editor is User where hasClaim("editor"). Authentication and authorisation stay distinct — a missing token is a 401, a non-editor token a 403, both enforced at the boundary before any handler runs.

The service exposes four routes. GET /flags calls Kv.list(Some("flag:")) and maps each key back through nameOf; GET /flags/:name reads one entry and decodes it through Json.decode[Flag], returning NotFound for a missing key and ServerError for a corrupt value rather than a silent undefined. The two writes, PUT /flags/:name and DELETE /flags/:name, are bound by e: Editor and call Kv.put (with Json.encode) and Kv.delete. The stored Flag record is just { enabled: Bool, description: String }.