Manage Flipt feature flags - list, create, enable/disable, and configure rollout rules. Use when you need to control feature flag state or set up segmented rollouts.
Use this skill to manage Flipt feature flags for controlled feature rollouts.
Use the included script:
node .claude/skills/flipt/flipt.mjs <command> [options]
| Command | Description |
|---|---|
list |
List all flags |
get <key> |
Get details for a specific flag |
create / enable / disable / delete |
Refuse with exit 2 and print the GitOps steps ā see below |
add-variant / remove-variant / set-rollout |
Same |
Reads work over the API; writes are deliberately refused. This Flipt is v2, GitOps-backed by a private state repo, so a merged change becomes visible within about a poll interval.
Never write through the API. Flag state is reviewed material ā change flags by opening a PR against the state repo, never by calling the API. This skill refuses writes for that reason.
The monolith's flag registry (src/server/services/feature-flags.service.ts) declares a
static availability per flag, and Flipt overrides it in both directions ā except on a flag
whose FEATURE_FLAG_<KEY> environment variable was APPLIED, which is removed from Flipt's
control entirely (createFeatureFlags records it in envOverriddenFlags; hasFeature then
skips Flipt for it).
Whether a variable is applied has exactly one rule, and it is not "is the variable set":
| Registry entry | Its FEATURE_FLAG_<KEY> variable |
Flipt |
|---|---|---|
availability: [] with a fliptKey |
ignored ā cannot switch it on | keeps control |
| anything else | applied | skipped entirely |
š“ So a NON-dark flag can carry a fliptKey, have that key set enabled: false, and still be
on for everyone. Toggling it in flag state changes nothing and looks like Flipt is broken.
Before you read such a flag's Flipt value as its effective state ā or set one expecting an effect
ā check whether a variable names it in the environment you care about.
š“ Do not invert that check on a dark flag. A variable naming an availability: [] flag that
has a key is discarded, so the flag is dark, off, and still Flipt-owned ā the opposite of pinned
out. The server logs a [feature-flags] warning naming each one it discards at startup.
Consequences before you add or toggle a key:
availability: [] that has a fliptKey is dark: static evaluation is
false for everyone and that key is its only on-switch. A FEATURE_FLAG_<KEY> environment
variable cannot lift it. Creating the key and enabling it is what ships the feature.availability: [] with no fliptKey has no Flipt switch at all, so its
FEATURE_FLAG_<KEY> variable still applies and is the only way to turn it on. Today that is
coinbasePayments and nowpaymentPayments, both written in the legacy array form
(coinbasePayments: []), which a search for availability: [] does not find. Adding a
fliptKey to one of these moves ownership to Flipt and makes its variable inert.['public'] (or any role) is live: creating its key with
enabled: false and no rollout turns the feature off for everyone ā the intended kill switch,
and the intended accident. Only if no FEATURE_FLAG_<KEY> variable names it. If one does,
that kill switch is inert and nothing reports it.To switch on a dark flag that has a fliptKey locally, set
FLIPT_LOCAL_OVERRIDES=<fliptKey>=on rather than touching shared flag state; it is ignored when
NODE_ENV=production, and it does not reach getFliptBoolean call sites. For a dark flag with
no key there is nothing to name here ā use its FEATURE_FLAG_<KEY> variable.
Treat FLIPT_API_TOKEN as a live secret: read it from env, never inline it, and
don't copy it into anything new.
Flags live at /api/v2/environments/{env}/namespaces/{ns}/resources/flipt.core.Flag.
The environment (civitai-app) and namespace (default) are discovered at
runtime ā the environment marked default: true, then its default namespace ā
so a second environment can't silently redirect reads. Override with
FLIPT_ENVIRONMENT / FLIPT_NAMESPACE if ever needed.
Responses carry a revision equal to the flipt-state commit SHA, which is the
quickest way to confirm a flag change has actually synced:
node .claude/skills/flipt/flipt.mjs list --json | head -3 # revision == your commit
| Flag | Description |
|---|---|
--description <text>, -d |
Description for new flag |
--enabled |
Create flag as enabled (default: disabled) |
--variant |
Create as variant flag (default: boolean) |
--variants <keys> |
Comma-separated variant keys (first is default) |
--default <key> |
Set default variant key |
--rollout <pct> |
Rollout percentage (default: 100) |
--segment <key> |
Segment key for rules (default: all-users) |
--json |
Output results as JSON |
--quiet, -q |
Minimal output |
--force, -f |
Skip confirmation prompts |
# List all flags
node .claude/skills/flipt/flipt.mjs list
# Get a specific flag
node .claude/skills/flipt/flipt.mjs get gift-card-vendor-waifu-way
# Creating / toggling a flag goes through GitOps ā see below.
# These commands exit 2 and print the steps:
node .claude/skills/flipt/flipt.mjs disable my-feature
# JSON output for scripting
node .claude/skills/flipt/flipt.mjs list --json
Flipt uses GitOps - flags are stored in the civitai/flipt-state repository. Changes made via the API are temporary and will be overwritten on the next Git sync (every 30 seconds).
For permanent changes, edit the repository directly:
# Clone the state repo
gh repo clone civitai/flipt-state /tmp/flipt-state
# Edit civitai-app/default/features.yaml
# Add your flag under the `flags:` section
# Commit and push
cd /tmp/flipt-state
git add -A && git commit -m "Add new feature flag" && git push
flags:
- key: my-feature-flag
name: my-feature-flag
type: BOOLEAN_FLAG_TYPE
description: Description of what this flag controls
enabled: false
# Optional: rollout rules
rollouts:
- threshold:
percentage: 50
value: true
- segment:
keys:
- moderators
operator: OR_SEGMENT_OPERATOR
value: true
flipt-state mainCopy .env.example to .env and configure:
cp .claude/skills/flipt/.env.example .claude/skills/flipt/.env
The skill needs FLIPT_URL and FLIPT_API_TOKEN to connect to Flipt.
š“ THE SKILL'S OWN .env IS NOT OPTIONAL, AND THE ROOT .env IS NOT A SUBSTITUTE.
loadEnv reads this skill's .env and then the project root's, first value winning ā so the
root's FLIPT_URL IS picked up when this file is absent, and that is the trap rather than the
rescue. The root's FLIPT_URL is the BROWSER-FACING host: every path under it answers 302 to a
GitHub OAuth login, so no bearer token will ever work against it. Measured 2026-10-05, after this
skill's .env had been lost: /meta/info, /health, /api/v1/namespaces,
/api/v2/environments and /evaluate/v1/boolean all 302 to the OAuth provider.
So adding only a token to the root .env leaves the skill broken in a WORSE way than it is
now: the "FLIPT_URL and FLIPT_API_TOKEN must be set" error disappears and is replaced by HTML
login pages failing to parse as JSON ā a confusing failure instead of a clear one. Both values
belong in this skill's .env together.
FLIPT_FETCHER_SECRET, which the root .env does carry, is a DIFFERENT credential and is not
an API token ā it does not authenticate these calls.
Ask an infra owner for the API endpoint and a read/query token. The specifics are
deliberately not written here: this repository is public, and how to reach a production service
past its proxy is exactly what the Security section of CLAUDE.md keeps out of it.
Once you have it, protect it ā these files are one git clean from gone and this one has already
been lost once:
node .claude/skills/dev-server/cli.mjs wt env --backup # copies skill credentials outside the repo