Menu
Platform
AI
App store purchases
Database
Flags
Jobs and cron
Localization
Monitoring
Notifications
Payments
Queues
Sandboxes
Webhooks
Getting Started
Authentication
KV Store
Deploy & Infrastructure
Reference
Targeting rules
The order an evaluation decides in, and the context it decides from
An evaluation is one small decision made in a fixed order. Nothing about it is implicit: the flag's spec holds a default, an ordered list of rules, and a switch, and the answer comes from reading them in one order. That is what makes a rollout something you can reason about after the fact.
#The order of the decision
enabled. When it is false, every evaluation returnsdefault_valueand the rules are not read at all: the kill switch.- The rules, in order. The first rule whose conditions all hold decides the
evaluation, and a rule's
percentagecan hand part of that match on to the next rule instead. default_value, when no rule claims the evaluation.
#The default is a real answer
default_value is required, so there is always something to serve — that is the
first property of the design: an evaluation can never fail for want of a value.
A flag with no rules serves the default to everyone, and the default is also
what a request a rule cannot route falls back to. The answer carries the reason
default in that case, and disabled when the switch is off, so a value that
looked surprising in a log says which of the two happened.
#A rule
| Field | Type | What it is |
|---|---|---|
value | valuerequired | The value served when the rule matches, of the flag’s type. |
percentage | int32 | Serve value to evaluations whose bucket is below this, from 0 to 100; default 100. The rest fall through to the next rule. |
bucket_attribute | string | The context attribute hashed for the bucket: user_id, anonymous_id, or an attribute path; default user_id, else anonymous_id. An evaluation without it never matches a rule below 100 percent. |
description | string | What the rule is for. |
conditions | TargetingCondition[] | Conditions over the evaluation context; all must hold. None matches every evaluation. |
Rules are ordered and first match wins, so the list is a decision procedure rather than a set: a rule with no conditions at the top shadows every rule below it. That is also why conditions and percentage share one object — a rollout to one audience is one rule, and the audience is the conditions.
#Percentage rollouts
percentage is 0 to 100 and defaults to 100. It decides by bucket, not by
chance: the evaluation's bucket_attribute is hashed into a stable bucket, so
the same subject falls the same way on every request, and 10 percent stays 10
percent of subjects rather than 10 percent of requests. The evaluations outside
the percentage do not fall to the default — they fall through to the next rule,
which is what makes a gradual rollout a list of rules rather than a single one.
bucket_attribute names what to hash: user_id, anonymous_id, or a path into
the context's attributes. It defaults to user_id, and to anonymous_id when
there is no user. An evaluation that does not carry the attribute the bucket
needs never matches a rule below 100 percent, so a rule that buckets by
user_id does not match a request that has none.
#Conditions
| Field | Type | What it is |
|---|---|---|
attribute | stringrequired | user_id, anonymous_id, or a path into the context’s attributes, dot-separated (plan, device.os). Required, except with IN_SEGMENT and NOT_IN_SEGMENT. |
operator | ConditionOperatorrequired | The comparison. |
value | valuerequired | The value compared with; a list for IN and NOT_IN, a string for the string, regular expression and semantic version operators. |
The operators are the comparisons you would expect — equals, not_equals,
greater_than, greater_or_equal, less_than, less_or_equal, contains,
not_contains, starts_with, ends_with, in, not_in, matches, and the
four semantic version comparisons semver_greater_than,
semver_greater_or_equal, semver_less_than and semver_less_or_equal. Two of
them compare against a segment instead of a literal: in_segment and
not_in_segment name a segment by id, which is the one case where a condition
needs no attribute of its own.
All the conditions of a rule must hold, and a rule with no conditions matches
every evaluation. matches is a regular expression, which is why a rule that
leans on it is worth describing in its description.
#The evaluation context
Every evaluation carries an EvaluationContext, and that is the only input a
rule reads:
- user_id
- The signed-in user; the default bucketing subject.
- anonymous_id
- A stable id of a device or visitor; the bucketing subject when there is no user_id.
- attributes
- Anything else rules compare, for example plan, country, app_version.
Nothing else about the request is consulted. A rule that reads plan reads it
from attributes, so the answer depends on what the caller said the subject is,
not on where the call came from.
#What evaluate answers
evaluate takes the environment, the context, and the ids of the flags to
evaluate; an empty list evaluates every flag of the environment, and an id the
environment does not have answers not_found rather than failing the call.
Each flag in the answer carries four things:
| Field | Type | What it is |
|---|---|---|
config_flag | string | The flag’s id. |
value | value | The value; null when the flag does not exist. |
reason | EvaluationReason | Why this value. One of targeting_match, split, default, disabled, not_found. |
rule_index | int32 | The index in spec.rules of the rule that matched, for TARGETING_MATCH and SPLIT. |
reason is the field that makes a rollout debuggable: it says why this value
rather than only which value, and it separates an id that does not exist from a
flag that is switched off. rule_index goes further and names which rule
decided it, in the order you wrote, for the two reasons a rule produces.