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:
- Disabled check — if the flag is disabled, return the default value with reason
DISABLED - Overrides — identity-based value overrides bypass all rules
- Prerequisites — all prerequisite flags must match expected values
- Targeting rules — evaluate each rule's conditions against the context (with schedule gating)
- Variant selection — if no rule matches, check variants with percentage-based rollout
- 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
| Operator | Description | Value Type | Example |
|---|---|---|---|
equals | Exact match | string | number | boolean | {"property": "plan", "operator": "equals", "value": "enterprise"} |
not_equals | Not equal | string | number | boolean | {"property": "status", "operator": "not_equals", "value": "banned"} |
in | Value in list | Array | {"property": "country", "operator": "in", "value": ["AU", "NZ", "US"]} |
not_in | Value not in list | Array | {"property": "plan", "operator": "not_in", "value": ["free", "trial"]} |
contains | String contains | string | {"property": "email", "operator": "contains", "value": "@acme.com"} |
starts_with | String prefix | string | {"property": "email", "operator": "starts_with", "value": "admin"} |
ends_with | String suffix | string | {"property": "email", "operator": "ends_with", "value": ".gov.au"} |
regex | Regex match | string (pattern) | {"property": "email", "operator": "regex", "value": "^.*@(acme|corp)\\.com$"} |
greater_than | Numeric > | number | {"property": "age", "operator": "greater_than", "value": 18} |
less_than | Numeric < | number | {"property": "age", "operator": "less_than", "value": 65} |
gte | Numeric >= | number | {"property": "version", "operator": "gte", "value": 2} |
lte | Numeric <= | number | {"property": "version", "operator": "lte", "value": 5} |
before | Date before | string (ISO date) | {"property": "signupDate", "operator": "before", "value": "2025-01-01"} |
after | Date after | string (ISO date) | {"property": "signupDate", "operator": "after", "value": "2024-06-01"} |
in_cohort | Member of cohort | boolean + cohortId | {"property": "targetingKey", "operator": "in_cohort", "cohortId": "cohort-beta"} |
not_in_cohort | Not in cohort | boolean + 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:
- Concatenates
flagKeyandtargetingKeyinto a hash input - Computes a 32-bit integer hash
- Takes
hash % 100to get a value 0–99 - 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:
| Reason | Description |
|---|---|
DISABLED | Flag is disabled, returned default value |
OVERRIDE | An identity override matched |
PREREQUISITE_FAILED | A prerequisite flag didn't match |
TARGETING_MATCH | A targeting rule's conditions matched |
VARIANT | Matched via variant percentage distribution |
DEFAULT | No rule matched, returned default value |
NOT_FOUND | Flag key doesn't exist |
ERROR | Evaluation error occurred |
CACHED | Returned from cache |
STATIC | Static/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 }
]
}- Banned users are always excluded
- Enterprise users always get the feature
- Everyone else gets a 10% gradual rollout
Best Practices
- Always include
targetingKeyin 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
regexsparingly — it's powerful but harder to debug - Test with the evaluation API before relying on client-side SDKs
Related
- Cohorts — Reusable audience segments for targeting
- Progressive Rollouts — Staged deployment with safety checks
- Overrides & Prerequisites — Identity overrides and flag dependencies
- Scheduled Rules — Time-based rule activation
- REST Evaluation API — Evaluate flags via HTTP
- Protocols — Connect-RPC, gRPC, and OFREP evaluation
- Environments — Per-environment targeting