Skip to main content

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

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