| name | add-feature-flag |
| description | Add a runtime feature flag (AppConfig-backed on prod, secret fallback off-prod), global by default or optionally gated by org id, user id, or platform admin |
| argument-hint | <flag-name> |
Add Feature Flag Skill
You add a runtime feature flag to Sim that can change on prod with no redeploy (AWS AppConfig). Prefer a global on/off flag unless the rollout actually needs per-organization, per-user, or platform-admin targeting. When AppConfig isn't the source of truth, the flag falls back to a single secret (on/off only).
When to use this vs env-flags.ts
- Feature flag (
@/lib/core/config/feature-flags.ts): runtime global on/off by default, optionally scoped by userId/orgId/admin. This skill.
- Env flag (
@/lib/core/config/env-flags.ts): deploy-time capability/environment detection (isProd, isHosted, isBillingEnabled). A module-load boolean. Do not add gated flags here.
If the user wants a fixed per-deployment toggle, send them to env-flags.ts instead.
The flag model
A flag's gating rule lives only in the hosted AppConfig document. It is ON for a context when any configured clause matches:
interface FeatureFlagRule {
enabled?: boolean
orgIds?: string[]
userIds?: string[]
adminEnabled?: boolean
}
Critically, none of this is expressible in code — gating (especially adminEnabled) can only be set through AppConfig, so no environment can grant access from a code literal. Off-AppConfig (self-hosted/OSS/local), a flag is simply on or off, derived from its fallback secret.
Steps
-
Confirm the granularity before editing code. If the user has not already specified it, stop and ask:
Should <flag-name> be a global on/off flag (recommended), or does it need rollout targeting by organization, user, and/or platform admin?
- Recommend global. Do not infer scoped gating merely because the call site already has a user or organization id.
- If the user chooses scoped gating but does not name the dimensions, ask which of organization, user, and platform admin it needs. Wire only the selected dimensions.
- If the user wants a fixed per-deployment toggle rather than a runtime AppConfig flag, use
env-flags.ts instead.
-
Define the flag. Add one entry to the FEATURE_FLAGS registry in apps/sim/lib/core/config/feature-flags.ts. Each entry is the flag's whole definition — name (kebab-case key), description, and the fallback secret consulted when AppConfig isn't the source of truth (truthy ⇒ on globally):
const FEATURE_FLAGS = {
'<flag-name>': {
description: '<what this gates>',
fallback: '<FLAG_SECRET>',
},
}
fallback is the env/secret key (typed as keyof typeof env), so add <FLAG_SECRET> to apps/sim/lib/core/config/env.ts first (and the deployment's secret store) — it won't typecheck otherwise. Do not add org/user/admin defaults here — that gating exists only in AppConfig. Adding the entry makes <flag-name> a valid FeatureFlagName.
-
Gate the call site at the chosen granularity. For the recommended global mode, pass no context:
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
if (await isFeatureEnabled('<flag-name>')) {
}
Notes
- Flag keys are
kebab-case.
- Never read flags via raw
fetch or a new AppConfig client — always go through isFeatureEnabled / getFeatureFlags.
- Never bake gating into code. The fallback is a single boolean secret; org/user/admin scoping is AppConfig-only.
- Never add or propagate request context unless the user chose scoped rollout.
- The admin check reads the DB replica (
dbReplica) and is resolved lazily, so an admin-gated flag adds at most one cheap replica read, and only when adminEnabled is the deciding clause.