Skip to main content

Feature Flags (release gating)

Aglyn staff only

Feature flags are managed from the staff console and require a staff claim; publishing changes requires the super staff role.

Release flags control whether a feature is launched — a separate axis from plan entitlements, which control whether an organization's plan includes a feature. A customer sees a feature only when it's released and their plan allows it.

The release flags page

How a flag is evaluated

Every check runs client-side against the activated Remote Config template, with the registry's code defaults as the offline fallback:

The rollout hash is seeded with the flag key and the organization, so a workspace keeps a stable verdict across sessions and different flags don't share buckets.

How gating behaves

  • Customers with a flag off don't see the feature: its dashboard tab disappears and deep links show a "coming soon" notice instead of the page content.
  • Staff always see every feature. A flagged-off feature is marked with a flag icon on its nav tab and a warning banner on the page, so an unreleased surface is never mistaken for a launched one.
  • Percentage rollout: a flag that's off can be rolled out gradually. The verdict is deterministic per workspace (hashed from the org), so a workspace keeps the feature once its bucket is inside the rollout percentage — no flapping between sessions.

Managing flags

Open Staff → Feature flags. Each registered flag shows its live value from Firebase Remote Config:

  • Toggle — on for everyone, or off (with optional rollout).
  • Rollout slider — 0–100% of workspaces while the flag is off.
  • Note — free-form context ("waiting on AGL-199", owner, launch date).

Publishing is immediate and global: clients pick up changes on their next Remote Config fetch (within an hour, or on the next full page load). Every publish writes an entry to the audit log, and concurrent edits are etag-guarded — if another staff member published first, the page reloads the latest values instead of overwriting them.

Reads are open to all staff roles; publishing requires the super role, matching user management.

Under the hood

  • Flags live as JSON parameters (release_*) in the Firebase Remote Config template; cloud/firebase-remoteconfig.template.json seeds them (firebase deploy --only remoteconfig).
  • The registry of flags — keys, labels, code-side fallback defaults, and which nav tab each governs — is versioned in @aglyn/aglyn (release-flags.ts). Adding a flag means adding a registry entry, seeding the template, and wrapping the page in <FeatureGate>.
  • If Remote Config is unreachable, the registry defaults gate — a default-off feature never flashes on while offline.
  • The registry default and the seeded value must agree, and a spec now enforces it (release-flags-template.spec.ts). They are read in different situations — the code default when Remote Config is unreachable, the template when it is not — so a disagreement ships the same build with a feature on in one environment and off in another, with neither looking wrong enough to be reported. The spec also fails on a registry flag that was never seeded, and on a seeded release_* no code declares.

A flag is not always sufficient on its own

release_native_checkout (in-page Stripe checkout, AGL-1132 and AGL-1944) is gated on the flag and on NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY being set. A ui_mode Checkout session returns a client secret and no redirect URL, so a browser that cannot mount the form has nowhere to send the buyer — flipping the flag without the key would turn Upgrade into a dead button, and a storefront's Buy button into one too. Both surfaces require both and otherwise serve the redirect.

The key is per Vercel project, and the two surfaces do not share one. Measured 2026-08-18: NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY is set on aglyn-console and is set nowhere on aglyn-tenant, which is the project that serves customer storefronts. So turning this flag on today changes the console and changes nothing at all on a storefront — which is safe, and is also why "I flipped it and nothing happened" is the expected report until the key is promoted to a team-shared variable and linked to both projects (the pattern STRIPE_SECRET_KEY already follows). Note that vercel env ls cannot see team-shared variables; check through the REST API.

Worth copying whenever a flag turns on a path that needs configuration the flag does not itself provide: gate on the capability, not just on the intent.