Skip to main content

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

FieldNotes
nameMax 200 characters
descriptionMax 1000 characters
enabledBoolean
defaultValueMust match the flag type
tagsMax 20 tags, each max 50 characters
variantsReplaces the full variant list
targetingReplaces the full targeting rules list
overridesReplaces the full overrides list
isPublicWhether the flag can be evaluated without authentication

What happens on update

Every update triggers the same pipeline as creation:

  1. Validation — Zod schema checks all fields
  2. Approval check — if the environment has requireApproval: true, returns HTTP 202 with a change request instead
  3. Storage write — persists the update
  4. Version snapshot — records the new state for rollback
  5. Audit log — captures who changed what
  6. Cache invalidation — clears all related caches
  7. Real-time event — SSE/Pub-Sub clients receive an UPDATED event

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 TypeToggle OnToggle Off
booleanenabled: true, defaultValue: trueenabled: false, defaultValue: false
stringenabled: true (defaultValue unchanged)enabled: false (defaultValue unchanged)
numberenabled: true (defaultValue unchanged)enabled: false (defaultValue unchanged)
objectenabled: 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:

  1. Disabled check — if flag is off, return defaultValue with reason DISABLED
  2. Overrides — exact identity match (highest priority first)
  3. Prerequisites — all prerequisite flags must pass
  4. Mutual exclusion — group constraints checked
  5. Experiments — running experiment variant assignment
  6. Targeting rules — first match wins (with schedule gating)
  7. Variants — weighted random assignment if no rule matched
  8. DefaultdefaultValue returned

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:

  1. All Pro/Enterprise users get true
  2. 25% of US users get true (gradual rollout)

Condition operators

OperatorValue TypeDescriptionExample
equalsanyExact matchcountry == "US"
not_equalsanyNot equalenv != "test"
inarrayValue in listplan in ["pro", "enterprise"]
not_inarrayValue not in listrole not_in ["banned"]
containsstringSubstring matchemail contains "@company.com"
starts_withstringPrefix matchname starts_with "admin"
ends_withstringSuffix matchemail ends_with ".edu"
greater_thannumberNumeric comparisonage > 18
less_thannumberNumeric comparisonscore < 100
gtenumberGreater or equalversion >= 2
ltenumberLess or equalattempts <= 3
regexstringRegular expressionemail matches ^.*@company\.com$
beforestring (ISO date)Date comparisoncreatedAt before 2026-01-01
afterstring (ISO date)Date comparisonsignupDate after 2025-06-01
in_cohortstring (cohort ID)Cohort membershipUser is in cohort
not_in_cohortstring (cohort ID)Not in cohortUser 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 targetingKey is 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

FieldTypeDescription
namestringHuman-readable label (required, max 100 chars)
descriptionstringWhy this override exists (max 500 chars)
identifiersstring[]Matching targetingKey values (1-100 identifiers)
valueanyThe value to return (must match flag type)
prioritynumberHigher 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
  1. Create and configure the flag in development
  2. Test thoroughly with targeting rules and overrides
  3. Promote to staging for QA validation
  4. Promote to production with 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

  1. Check impact — use the impact analysis endpoint to see current evaluation volume
  2. Disable first — disable the flag and wait for a release cycle to ensure no code depends on it
  3. Remove from code — clean up all evaluateFlag("old-checkout-flow", ...) calls
  4. 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:

ReasonDescription
DISABLEDFlag is off — returned defaultValue
OVERRIDEMatched a per-user override
PREREQUISITE_FAILEDA prerequisite flag didn't meet its expected value
MUTUAL_EXCLUSIONBlocked by a mutual exclusion group constraint
EXPERIMENTAssigned a variant by a running experiment
TARGETING_MATCHMatched a targeting rule
VARIANTAssigned a variant by weighted distribution (no targeting rule matched)
DEFAULTNo rules matched — returned defaultValue
NOT_FOUNDFlag doesn't exist in this environment
ERRORAn error occurred during evaluation

Use these reasons in logging and debugging to understand flag behavior.