Day-to-day flag operations — editing, toggling, targeting rules, rollouts, overrides, variants, and environment promotion
Managing Flags
This guide covers everything you need to manage feature flags after creation: editing properties, toggling, adding targeting rules, configuring rollouts, setting per-user overrides, working with variants, and promoting flags across environments.
Editing Flag Properties
Via the Dashboard
Open a flag's detail panel and edit any mutable property inline. Editable fields include:
- Name and description
- Default value (must still match the flag's type)
- Tags — add or remove labels
- Enabled state
- Variants, targeting rules, and overrides
The flag type (boolean, string, number, object) is immutable after creation.
Via the API
curl -X PATCH "https://flaggr.dev/api/flags/new-checkout-flow?serviceId=svc-abc123&environment=development" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "New Checkout Flow v2",
"description": "Updated single-page checkout with express options",
"tags": ["checkout", "q2-launch"]
}'Only include the fields you want to change. The response contains the full updated flag object.
Updatable fields
| Field | Notes |
|---|---|
name | Max 200 characters |
description | Max 1000 characters |
enabled | Boolean |
defaultValue | Must match the flag type |
tags | Max 20 tags, each max 50 characters |
variants | Replaces the full variant list |
targeting | Replaces the full targeting rules list |
overrides | Replaces the full overrides list |
isPublic | Whether the flag can be evaluated without authentication |
What happens on update
Every update triggers the same pipeline as creation:
- Validation — Zod schema checks all fields
- Approval check — if the environment has
requireApproval: true, returns HTTP202with a change request instead - Storage write — persists the update
- Version snapshot — records the new state for rollback
- Audit log — captures who changed what
- Cache invalidation — clears all related caches
- Real-time event — SSE/Pub-Sub clients receive an
UPDATEDevent
Toggling Flags
Toggling is the most common flag operation. It changes the enabled state and, for boolean flags, also updates defaultValue.
Toggle behavior by type
| Flag Type | Toggle On | Toggle Off |
|---|---|---|
boolean | enabled: true, defaultValue: true | enabled: false, defaultValue: false |
string | enabled: true (defaultValue unchanged) | enabled: false (defaultValue unchanged) |
number | enabled: true (defaultValue unchanged) | enabled: false (defaultValue unchanged) |
object | enabled: true (defaultValue unchanged) | enabled: false (defaultValue unchanged) |
Boolean flags couple enabled and defaultValue together so that if (flag) works intuitively. Non-boolean flags only toggle enabled — their defaultValue stays the same.
Toggle via the API
# Toggle (flip current state)
curl -X POST "https://flaggr.dev/api/flags/new-checkout-flow/toggle?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>"
# Explicit set (idempotent — preferred for automation)
curl -X POST "https://flaggr.dev/api/flags/new-checkout-flow/toggle?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'The explicit { "enabled": true } form is idempotent — calling it twice produces the same result. Use this in scripts and CI/CD to avoid accidental double-toggles.
Disabled flag behavior
When a flag is disabled, all evaluations return:
{ "value": <defaultValue>, "reason": "DISABLED" }No targeting rules, overrides, or variants are evaluated. The DISABLED reason makes it clear in logs and traces why a specific value was returned.
Targeting Rules
Targeting rules let you return different values for different users based on their attributes.
Evaluation order
Rules are evaluated top-to-bottom. The first rule that matches determines the returned value:
- Disabled check — if flag is off, return
defaultValuewith reasonDISABLED - Overrides — exact identity match (highest priority first)
- Prerequisites — all prerequisite flags must pass
- Mutual exclusion — group constraints checked
- Experiments — running experiment variant assignment
- Targeting rules — first match wins (with schedule gating)
- Variants — weighted random assignment if no rule matched
- Default —
defaultValuereturned
Adding a targeting rule
curl -X PATCH "https://flaggr.dev/api/flags/new-checkout-flow?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"targeting": [
{
"id": "beta-users",
"conditions": [
{ "property": "plan", "operator": "in", "value": ["pro", "enterprise"] }
],
"value": true
},
{
"id": "us-users",
"conditions": [
{ "property": "country", "operator": "equals", "value": "US" }
],
"rolloutPercentage": 25,
"value": true
}
]
}'This configures two rules:
- All Pro/Enterprise users get
true - 25% of US users get
true(gradual rollout)
Condition operators
| Operator | Value Type | Description | Example |
|---|---|---|---|
equals | any | Exact match | country == "US" |
not_equals | any | Not equal | env != "test" |
in | array | Value in list | plan in ["pro", "enterprise"] |
not_in | array | Value not in list | role not_in ["banned"] |
contains | string | Substring match | email contains "@company.com" |
starts_with | string | Prefix match | name starts_with "admin" |
ends_with | string | Suffix match | email ends_with ".edu" |
greater_than | number | Numeric comparison | age > 18 |
less_than | number | Numeric comparison | score < 100 |
gte | number | Greater or equal | version >= 2 |
lte | number | Less or equal | attempts <= 3 |
regex | string | Regular expression | email matches ^.*@company\.com$ |
before | string (ISO date) | Date comparison | createdAt before 2026-01-01 |
after | string (ISO date) | Date comparison | signupDate after 2025-06-01 |
in_cohort | string (cohort ID) | Cohort membership | User is in cohort |
not_in_cohort | string (cohort ID) | Not in cohort | User is not in cohort |
Condition logic (AND / OR)
By default, all conditions within a rule use AND logic — every condition must match. Change this with conditionOperator:
{
"id": "mobile-or-tablet",
"conditions": [
{ "property": "device", "operator": "equals", "value": "mobile" },
{ "property": "device", "operator": "equals", "value": "tablet" }
],
"conditionOperator": "or",
"value": true
}Condition groups
For complex logic like (A AND B) OR (C AND D), use condition groups:
{
"id": "complex-targeting",
"groups": [
{
"conditions": [
{ "property": "country", "operator": "equals", "value": "US" },
{ "property": "plan", "operator": "equals", "value": "enterprise" }
]
},
{
"conditions": [
{ "property": "country", "operator": "equals", "value": "UK" },
{ "property": "plan", "operator": "in", "value": ["pro", "enterprise"] }
]
}
],
"groupOperator": "or",
"value": true
}Conditions within a group use AND logic. Groups are combined with groupOperator (default: "or").
Scheduled rules
Rules can be gated by a time schedule so they only activate during specific windows:
{
"id": "business-hours-only",
"conditions": [
{ "property": "region", "operator": "equals", "value": "US" }
],
"schedule": {
"startDate": "2026-03-01T00:00:00Z",
"endDate": "2026-06-01T00:00:00Z",
"recurrence": {
"type": "weekly",
"daysOfWeek": [1, 2, 3, 4, 5],
"startTime": "09:00",
"endTime": "17:00"
}
},
"value": true
}Schedule fields:
startDate/endDate— overall date range (ISO 8601)recurrence.type—"daily","weekly", or"cron"recurrence.daysOfWeek—[0-6]where 0 is Sunday (UTC)recurrence.startTime/endTime—"HH:MM"in UTC
All times use UTC for deterministic behavior across serverless instances.
Percentage Rollouts
Gradual rollouts let you expose a flag to a percentage of users. Rollouts use deterministic hashing — the same user always falls in the same bucket.
How rollout hashing works
hash(flagKey + ":rollout:" + targetingKey) % 100
If the hash result is less than rolloutPercentage, the user is in the rollout. This means:
- Increasing the percentage from 10% to 25% adds new users but never removes existing ones
- The same user sees the same behavior across requests (sticky)
- A
targetingKeyis required — without one, rollout rules are skipped
Configuring a rollout
Add rolloutPercentage to a targeting rule:
{
"id": "gradual-release",
"conditions": [],
"rolloutPercentage": 10,
"value": true
}An empty conditions array matches everyone — the rollout percentage is the only gate. To combine targeting with rollout:
{
"id": "us-gradual",
"conditions": [
{ "property": "country", "operator": "equals", "value": "US" }
],
"rolloutPercentage": 50,
"value": true
}This targets 50% of US users.
Ramping up a rollout
Increase rolloutPercentage gradually: 5% → 10% → 25% → 50% → 100%. At each step, monitor health metrics and error rates before proceeding.
Per-User Overrides
Overrides let you force a specific value for individual users, bypassing all targeting rules and rollouts.
Adding overrides
curl -X PATCH "https://flaggr.dev/api/flags/new-checkout-flow?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"overrides": [
{
"name": "QA testers",
"description": "Always-on for QA team",
"identifiers": ["user-qa-1", "user-qa-2", "user-qa-3"],
"value": true,
"priority": 10
},
{
"name": "Bug report user",
"description": "Disabled for user reporting layout issues",
"identifiers": ["user-12345"],
"value": false,
"priority": 5
}
]
}'Override fields
| Field | Type | Description |
|---|---|---|
name | string | Human-readable label (required, max 100 chars) |
description | string | Why this override exists (max 500 chars) |
identifiers | string[] | Matching targetingKey values (1-100 identifiers) |
value | any | The value to return (must match flag type) |
priority | number | Higher priority wins (0-1000, default 1) |
Override evaluation
Overrides are checked before all targeting rules. They are sorted by priority (highest first) and matched against the evaluation context's targetingKey. The first matching override wins.
Common use cases
- QA testing: Force a feature on/off for testers in production
- VIP access: Give early access to specific accounts
- Bug workaround: Disable a broken feature for affected users
- Demo accounts: Ensure demo users see specific behavior
Working with Variants
Variants enable A/B testing by returning different values to different users with configurable weights.
Adding variants
curl -X PATCH "https://flaggr.dev/api/flags/pricing-layout?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"variants": [
{ "name": "control", "value": "grid", "weight": 50 },
{ "name": "treatment_a", "value": "comparison-table", "weight": 30 },
{ "name": "treatment_b", "value": "single-column", "weight": 20 }
]
}'How variant assignment works
When a flag has variants and a targetingKey is provided:
hash(flagKey + targetingKey) % 100
The hash falls into a weight bucket:
- 0-49 →
control(weight 50) - 50-79 →
treatment_a(weight 30) - 80-99 →
treatment_b(weight 20)
Assignment is sticky — the same user always gets the same variant. Weights are relative and should sum to 100 for clarity.
Combining variants with targeting
A targeting rule can return a specific variant:
{
"id": "internal-always-treatment",
"conditions": [
{ "property": "email", "operator": "ends_with", "value": "@company.com" }
],
"variant": "treatment_a",
"value": "comparison-table"
}This forces all internal users to see treatment_a, while external users get random variant assignment based on weights.
Variant evaluation response
{
"value": "comparison-table",
"variant": "treatment_a",
"reason": "VARIANT"
}The variant field tells your code which variant was assigned, separate from the value.
Environment Promotion
Flags exist independently in each environment. After testing a flag configuration in development, you can promote it to staging or production.
Promote via the API
curl -X POST "https://flaggr.dev/api/projects/proj-123/environments/development/promote" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"targetEnvironment": "staging",
"serviceId": "svc-abc123",
"flagKeys": ["new-checkout-flow", "pricing-layout"]
}'Promotion copies the flag configuration (targeting rules, variants, overrides, default value) from the source environment to the target. The flag starts disabled in the target environment regardless of its state in the source — you must explicitly enable it.
Typical promotion flow
development → staging → production
- Create and configure the flag in
development - Test thoroughly with targeting rules and overrides
- Promote to
stagingfor QA validation - Promote to
productionwith a gradual rollout
Each environment is independent — changes in development don't affect staging or production.
Protected environments
When an environment has requireApproval: true, any flag change returns HTTP 202 Accepted with a change request. The change must be approved by a project admin before it takes effect. This is common for production environments.
Bulk Operations
For managing multiple flags at once, use the bulk API endpoints.
Bulk create
curl -X POST "https://flaggr.dev/api/flags/bulk" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"flags": [
{ "key": "feature-a", "name": "Feature A", "type": "boolean", "defaultValue": false, "serviceId": "svc-abc123" },
{ "key": "feature-b", "name": "Feature B", "type": "boolean", "defaultValue": false, "serviceId": "svc-abc123" }
]
}'Bulk update
curl -X PATCH "https://flaggr.dev/api/flags/bulk" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"flags": [
{ "key": "feature-a", "serviceId": "svc-abc123", "updates": { "enabled": true } },
{ "key": "feature-b", "serviceId": "svc-abc123", "updates": { "enabled": true } }
]
}'Bulk operations support up to 100 flags per request.
Deleting Flags
Via the API
curl -X DELETE "https://flaggr.dev/api/flags/old-checkout-flow?serviceId=svc-abc123&environment=production" \
-H "Authorization: Bearer <token>"Deletion requires delete permission on the project (Owner or Admin role).
Before deleting
- Check impact — use the impact analysis endpoint to see current evaluation volume
- Disable first — disable the flag and wait for a release cycle to ensure no code depends on it
- Remove from code — clean up all
evaluateFlag("old-checkout-flow", ...)calls - Delete — remove the flag from each environment independently
Deletion is permanent — use version history to review previous configurations if needed.
Evaluation Reasons
Every flag evaluation returns a reason field explaining why a specific value was chosen:
| Reason | Description |
|---|---|
DISABLED | Flag is off — returned defaultValue |
OVERRIDE | Matched a per-user override |
PREREQUISITE_FAILED | A prerequisite flag didn't meet its expected value |
MUTUAL_EXCLUSION | Blocked by a mutual exclusion group constraint |
EXPERIMENT | Assigned a variant by a running experiment |
TARGETING_MATCH | Matched a targeting rule |
VARIANT | Assigned a variant by weighted distribution (no targeting rule matched) |
DEFAULT | No rules matched — returned defaultValue |
NOT_FOUND | Flag doesn't exist in this environment |
ERROR | An error occurred during evaluation |
Use these reasons in logging and debugging to understand flag behavior.
Related
- Creating Flags — initial flag setup and configuration
- Monitoring Flags — health scoring, impact analysis, and debugging
- Targeting Rules — deep dive into targeting operators
- Rollouts — gradual rollout strategies
- Overrides & Prerequisites — advanced override patterns
- Experiments — A/B testing configuration