Skip to main content

Configure conditional flag delivery with targeting rules, conditions, operators, and gradual rollouts

Last updated April 4, 2026

Targeting Rules

Targeting rules let you control exactly who sees a feature flag based on evaluation context properties. Rules are evaluated server-side and support complex conditions, nested groups, and gradual rollouts.

How Evaluation Works

When a flag is evaluated, Flaggr follows this sequence:

  1. Disabled check — if the flag is disabled, return the default value with reason DISABLED
  2. Overrides — identity-based value overrides bypass all rules
  3. Prerequisites — all prerequisite flags must match expected values
  4. Targeting rules — evaluate each rule's conditions against the context (with schedule gating)
  5. Variant selection — if no rule matches, check variants with percentage-based rollout
  6. Default value — if nothing matches, return the default with reason DEFAULT

Evaluation Context

Every evaluation can include a context object describing the request:

{
  "context": {
    "targetingKey": "user-123",      // Required for consistent rollouts
    "email": "alice@example.com",
    "plan": "enterprise",
    "country": "AU",
    "betaUser": true,
    "signupDate": "2025-06-15"
  }
}

The targetingKey is critical — it's used for consistent hashing in percentage rollouts, ensuring the same user always gets the same variant.

Rule Structure

Each targeting rule contains conditions and an action:

{
  "targeting": [
    {
      "id": "enterprise-users",
      "conditions": [
        { "property": "plan", "operator": "in", "value": ["enterprise", "business"] },
        { "property": "country", "operator": "equals", "value": "AU" }
      ],
      "conditionOperator": "and",
      "value": true
    }
  ]
}

When a rule matches, the flag returns the rule's value (or selects the named variant) with reason TARGETING_MATCH.

Condition Operators

OperatorDescriptionValue TypeExample
equalsExact matchstring | number | boolean{"property": "plan", "operator": "equals", "value": "enterprise"}
not_equalsNot equalstring | number | boolean{"property": "status", "operator": "not_equals", "value": "banned"}
inValue in listArray{"property": "country", "operator": "in", "value": ["AU", "NZ", "US"]}
not_inValue not in listArray{"property": "plan", "operator": "not_in", "value": ["free", "trial"]}
containsString containsstring{"property": "email", "operator": "contains", "value": "@acme.com"}
starts_withString prefixstring{"property": "email", "operator": "starts_with", "value": "admin"}
ends_withString suffixstring{"property": "email", "operator": "ends_with", "value": ".gov.au"}
regexRegex matchstring (pattern){"property": "email", "operator": "regex", "value": "^.*@(acme|corp)\\.com$"}
greater_thanNumeric >number{"property": "age", "operator": "greater_than", "value": 18}
less_thanNumeric <number{"property": "age", "operator": "less_than", "value": 65}
gteNumeric >=number{"property": "version", "operator": "gte", "value": 2}
lteNumeric <=number{"property": "version", "operator": "lte", "value": 5}
beforeDate beforestring (ISO date){"property": "signupDate", "operator": "before", "value": "2025-01-01"}
afterDate afterstring (ISO date){"property": "signupDate", "operator": "after", "value": "2024-06-01"}
in_cohortMember of cohortboolean + cohortId{"property": "targetingKey", "operator": "in_cohort", "cohortId": "cohort-beta"}
not_in_cohortNot in cohortboolean + cohortId{"property": "targetingKey", "operator": "not_in_cohort", "cohortId": "cohort-internal"}

Condition Groups

For complex logic, use condition groups with nested AND/OR operators:

{
  "targeting": [
    {
      "id": "complex-rule",
      "groups": [
        {
          "conditions": [
            { "property": "plan", "operator": "equals", "value": "enterprise" },
            { "property": "country", "operator": "in", "value": ["AU", "NZ"] }
          ],
          "operator": "and"
        },
        {
          "conditions": [
            { "property": "betaUser", "operator": "equals", "value": true }
          ],
          "operator": "and"
        }
      ],
      "groupOperator": "or",
      "value": true
    }
  ]
}

This reads as: Enterprise users in AU/NZ, OR any beta user.

Within each group, conditions are combined with the group's operator (default and). Groups are combined with groupOperator.

Gradual Rollouts

Use rolloutPercentage to gradually expose a feature to a percentage of users:

{
  "targeting": [
    {
      "id": "canary-rollout",
      "conditions": [
        { "property": "plan", "operator": "not_equals", "value": "free" }
      ],
      "rolloutPercentage": 25,
      "value": true
    }
  ]
}

This enables the feature for 25% of paying users. The percentage is determined by consistent hashing on flagKey + targetingKey, so each user consistently gets the same result across evaluations.

How Consistent Hashing Works

Flaggr uses a modified djb2 hash algorithm:

  1. Concatenates flagKey and targetingKey into a hash input
  2. Computes a 32-bit integer hash
  3. Takes hash % 100 to get a value 0–99
  4. If the value is less than rolloutPercentage, the user is included

This guarantees that user abc always gets the same outcome for flag checkout-v2, regardless of when or where the evaluation happens.

Variants

Variants allow A/B testing and multi-way splits with weighted distribution:

{
  "variants": [
    { "name": "control", "value": "classic-checkout", "weight": 50 },
    { "name": "treatment-a", "value": "new-checkout", "weight": 30 },
    { "name": "treatment-b", "value": "express-checkout", "weight": 20 }
  ]
}

Variant weights should sum to approximately 100. The variant assigned to a user is determined by the same consistent hashing mechanism, ensuring stable assignment.

When a variant is selected, the response includes:

{
  "value": "new-checkout",
  "variant": "treatment-a",
  "reason": "VARIANT"
}

Targeting Rules with Variants

Rules can reference specific variants instead of returning a literal value:

{
  "targeting": [
    {
      "id": "force-enterprise-to-treatment",
      "conditions": [
        { "property": "plan", "operator": "equals", "value": "enterprise" }
      ],
      "variant": "treatment-a"
    }
  ]
}

Evaluation Reasons

Every evaluation response includes a reason field:

ReasonDescription
DISABLEDFlag is disabled, returned default value
OVERRIDEAn identity override matched
PREREQUISITE_FAILEDA prerequisite flag didn't match
TARGETING_MATCHA targeting rule's conditions matched
VARIANTMatched via variant percentage distribution
DEFAULTNo rule matched, returned default value
NOT_FOUNDFlag key doesn't exist
ERROREvaluation error occurred
CACHEDReturned from cache
STATICStatic/hardcoded value

Evaluation Order

Rules are evaluated top-to-bottom. The first matching rule wins. Design your rules with the most specific conditions first:

{
  "targeting": [
    { "id": "block-banned",     "conditions": [{"property": "banned", "operator": "equals", "value": true}], "value": false },
    { "id": "enterprise-early", "conditions": [{"property": "plan", "operator": "equals", "value": "enterprise"}], "value": true },
    { "id": "gradual-rollout",  "conditions": [], "rolloutPercentage": 10, "value": true }
  ]
}
  1. Banned users are always excluded
  2. Enterprise users always get the feature
  3. Everyone else gets a 10% gradual rollout

Best Practices

  • Always include targetingKey in your evaluation context for consistent rollouts
  • Order rules from most to least specific — first match wins
  • Use condition groups for complex logic instead of duplicating rules
  • Start rollouts small (5–10%) and increase gradually
  • Use regex sparingly — it's powerful but harder to debug
  • Test with the evaluation API before relying on client-side SDKs