SaaS Starter
Platform admin

Feature flags

Runtime toggles + percentage rollouts, audited.

/admin/feature-flags lists every flag with its current state. Each row exposes a master on/off Switch and a numeric rollout-pct input.

Routes

GET   /api/v1/platform/feature-flags
PATCH /api/v1/platform/feature-flags/{key}

PATCH body is partial — any field you omit keeps its current value. Supported fields:

{
  enabled?: boolean;
  rolloutPct?: number | null;       // 0..100, clamped server-side
  description?: string | null;
  enabledOrgIds?: string[];          // user-level allow-list (override)
  enabledUserIds?: string[];         // org-level allow-list (override)
}

Audit: platform.feature_flag.set with { before: { enabled, rolloutPct }, after: { enabled, rolloutPct } }.

Resolution order

EvaluateFlagUseCase resolves in this order (first match wins):

  1. User allow-listenabledUserIds.includes(userId) → on.
  2. Org allow-listenabledOrgIds.includes(organizationId) → on.
  3. Master switchenabled === false → off.
  4. Rollout — bucket (key, principal) to [0, 100) via sha256 and compare to rolloutPct. Same input → same bucket across processes, no shared cache.

The bucket is keyed by userId if present, otherwise organizationId (so anonymous traffic deterministically lands in the same bucket per session).

Creating a flag

Flags are created via the tenant-scoped PUT /api/v1/feature-flags (also requirePlatformAdmin-gated, separate router). The admin console only exposes the cross-tenant modify path because the typical admin task is "flip what's already there", not "create new". Use the upsert endpoint or seed flags from your migration script.

curl -X PUT http://localhost:3005/api/v1/feature-flags \
  -H 'Content-Type: application/json' \
  -b "$(cookie-jar)" \
  -d '{"key":"new-dashboard","description":"New dashboard layout","enabled":false,"rolloutPct":null}'

Rollout strategy

Common patterns:

  • Internal dogfoodenabledUserIds: [...staffIds], enabled: false, rolloutPct: null.
  • Gradual rolloutenabled: true, rolloutPct: 5 → 10 → 25 → 50 → 100.
  • Allow-list specific tenantsenabledOrgIds: [...] plus enabled: false.
  • Kill switchenabled: false overrides every other setting except the explicit user/org allow-lists.

On this page