Feature Flags
Feature Flags
Feature flags are 100% Cloudflare-native via Cloudflare Flagship — there is no bespoke flag storage or evaluation logic anywhere in this codebase. Flags, targeting rules, and percentage rollouts are configured from Compute > Flagship in the Cloudflare dashboard and propagate globally within seconds — no redeploy required.
This fully replaces two earlier homegrown systems, both removed:
- A D1-backed
feature_flagstable with hand-rolled rollout-percentage/tier/user targeting logic (worker/services/admin-feature-flag-service.tsused to query it directly; now it proxies the Flagship management API instead). - A simple KV-backed on/off flag store (
worker/services/feature-flag-service.ts, deleted). It was never used in production, so no migration of existing flag state was needed.
Flagship’s dashboard covers the same use cases (kill-switches, gradual rollouts, tier-gated betas, user overrides) with a real UI, audit history, and 11 comparison operators for targeting instead of bespoke SQL or KV JSON blobs.
Two separate concerns
| Concern | Mechanism | Needs an API token? |
|---|---|---|
| Evaluation — “is this flag on for this request?” | The [[flagship]] Worker binding (env.FLAGS), wrapped by worker/services/flagship-feature-flag-service.ts via the OpenFeature SDK | No — the binding authenticates automatically |
| Management — create/update/delete/list flags | The admin API proxy (/admin/config/feature-flags), backed by worker/services/admin-feature-flag-service.ts and src/services/cloudflareApiService.ts | Yes — FLAGSHIP_API_TOKEN with “Flagship Edit” permission |
Never call the management API on a hot request path — it’s an HTTP round-trip to api.cloudflare.com. Evaluation always goes through the binding.
Evaluating a flag in the Worker
import { createFlagshipFeatureFlagService } from '../services/flagship-feature-flag-service.ts';import { silentLogger } from '../../src/utils/index.ts';
const flags = createFlagshipFeatureFlagService(env.FLAGS, silentLogger);if (await flags?.isEnabled('new-checkout', false, { targetingKey: userId, plan: userTier })) { // ...}targetingKeyis the OpenFeature-reserved attribute percentage rollouts bucket on by default — pass it for stable per-user bucketing.isEnabled()never throws; evaluation failures (binding errors, an app that hasn’t propagated yet) resolve to thefallbackyou provide.getAllEnabled()iterates the compile-time-knownFEATURE_FLAG_KEYSarray (seesrc/platform/FeatureFlagService.ts) since Flagship’s binding has no “list all flags” call — add new flag keys there to include them.
Managing flags via the admin API
GET /admin/config/feature-flags — list flags in the configured appPOST /admin/config/feature-flags — create a flagPATCH /admin/config/feature-flags/{key} — partially update a flagDELETE /admin/config/feature-flags/{key} — permanently delete a flagAll routes require Admin tier + admin role (create/update/delete additionally require the flags:write permission on the legacy RBAC path) and return 503 if CF_ACCOUNT_ID, FLAGSHIP_APP_ID, or FLAGSHIP_API_TOKEN aren’t configured.
Create a boolean flag
curl -X POST https://api.bloqr.dev/admin/config/feature-flags \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "key": "streaming-api-beta", "enabled": true, "default_variation": "off", "variations": { "off": false, "on": true }, "rules": [], "description": "Server-sent events streaming API" }'Toggle a flag (PATCH only sends what changes)
PATCH merges your payload over the flag’s current definition before writing — Flagship’s own update endpoint replaces the entire flag, so admin-feature-flag-service.ts reads-then-writes so a simple toggle never wipes out existing rules or variations.
curl -X PATCH https://api.bloqr.dev/admin/config/feature-flags/streaming-api-beta \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{ "enabled": false }'Delete a flag
curl -X DELETE https://api.bloqr.dev/admin/config/feature-flags/streaming-api-beta \ -H "Authorization: Bearer $JWT"Targeting rules and percentage rollouts
Configured entirely in the Cloudflare dashboard (or via the same admin API, with a rules array) — not reimplemented locally. A rule has ordered conditions (attribute comparisons, optionally grouped with AND/OR, nested up to 5 levels), a serve_variation, and an optional rollout (percentage + sticky-bucketing attribute):
flowchart TD
A["Evaluate flag"] --> B{"Flag enabled?"}
B -->|No| C["Serve default_variation"]
B -->|Yes| D["Walk rules in ascending priority"]
D --> E{"Rule conditions match context?"}
E -->|No| D
E -->|"Yes, no rollout"| F["Serve rule's serve_variation"]
E -->|"Yes, with rollout"| G["Hash context[rollout.attribute] (default targetingKey)"]
G --> H{"Bucket < rollout.percentage?"}
H -->|Yes| F
H -->|No| D
D -->|"No rule matched"| C
The 11 supported operators: equals, not_equals, greater_than, less_than, greater_than_or_equals, less_than_or_equals, contains, starts_with, ends_with, in, not_in. See FlagshipConditionSchema / FlagshipRuleSchema in worker/schemas.ts for the exact request/response shape (Zod-validated at the trust boundary).
Configuration
[[flagship]]binding = "FLAGS"app_id = "<APP_ID>" # Compute > Flagship in the dashboard
[vars]FLAGSHIP_APP_ID = "<APP_ID>" # same app_id, exposed to the admin API proxywrangler secret put FLAGSHIP_API_TOKEN # account API token with "Flagship Edit" permissionSee the [[flagship]] block in wrangler.toml and the FLAGSHIP_APP_ID/FLAGSHIP_API_TOKEN comments in .dev.vars.example for the full setup checklist, including how to create the app.
When env.FLAGS is absent (no Flagship app configured yet, e.g. a fresh local
checkout before running the setup steps above), createFlagshipFeatureFlagService()
returns undefined and callers should treat every flag as disabled — there is
no separate fallback provider to fall back to.
What was removed
worker/services/feature-flag-service.ts—KvFeatureFlagService,NullFeatureFlagService,createFeatureFlagService(), and theFEATURE_FLAGSKV namespace binding. Deleted outright (not deprecated) since it was never used in production.- The D1
feature_flagstable — dropped via migration (seedocs/admin/database-schema.mdx).worker/services/admin-feature-flag-service.tsno longer touches D1 at all for flags.
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.