Targeting Rules: Operator Reference
This is the operator reference for targeting rules: what each operator matches, and the value it takes. Flag targeting and experiment targeting answer every operator on this page the same way.
Two shapes
Rules are saved in one of two shapes:
- Dashboard, the shape the dashboard's rule builder writes:
{"logical_operator", "groups": [{"logical_operator", "conditions": [{"attribute", "operator", "value"}]}]}. Its operators are the dashboard names in the table below. Segment rules take this shape only. - Native:
{"rules": [{"id", "rule": {"operator", "conditions", "groups"}, "rollout_percentage", "priority"}], "default_rule"}. A condition is{"attribute", "operator", "value", "additional_value"}, with the native names below. Groups can hold groups.
A list of rules, the legacy [{"type": ...}] shape, is refused when it is saved.
Creating Feature Flags lists what else is
refused, and how the request's context is matched against the attributes.
Each level combines its conditions and groups with AND (all of them hold), OR (at least
one holds) or NOT (not all of them hold).
Operators
A condition on an attribute the request's context does not carry does not hold, except
is_null; a segment condition reads no attribute.
| Dashboard | Native | The condition holds when |
|---|---|---|
equals |
eq |
the attribute equals the value. Text and a number or boolean are compared as that type ("17" equals 17, "true" equals true); two texts are compared as they are. |
not_equals |
neq |
the attribute does not equal the value, compared as equals compares. |
greater_than, greater_than_or_equal, less_than, less_than_or_equal |
gt, gte, lt, lte |
the attribute compares so with the value, both read as numbers: text that is a number ("21") counts. |
in |
in |
the attribute equals one of the values, compared as equals compares. |
not_in |
not_in |
the attribute equals none of the values. |
contains, not_contains |
contains, not_contains |
the attribute, as text, contains the value, or does not. |
starts_with, ends_with |
starts_with, ends_with |
the attribute starts, or ends, with the value. |
regex |
match_regex |
the attribute contains a match for the pattern (below). |
is_null |
is_null |
the attribute is missing or empty: null, blank text, or an empty list or object. |
is_not_null |
is_not_null |
the attribute is there and is not empty in that sense. |
array_contains |
contains_all |
the attribute is a list holding every value, or text containing every value. |
array_intersects |
contains_any |
the attribute is a list holding at least one of the values, or text containing one of them. |
semver_eq, semver_gt, semver_lt, semver_gte, semver_lte |
semantic_version, with additional_value eq, gt, lt, gte or lte |
the attribute is a version that compares so with the value (below). |
geo_within_radius |
geo_distance |
the attribute is a point within the radius (below). |
time_window |
time_window |
the attribute is a moment inside the window (below). |
in_segment, not_in_segment |
in_segment, not_in_segment |
the user is, or is not, a member of the segment (below). |
| none | between |
the attribute is a number, or a date, from value to additional_value, both included. |
| none | before, after |
the attribute is a date before, or after, the value (below). |
| none | array_length |
the attribute is a list, text or object whose length compares with the value as additional_value says: eq (the default), gt, gte, lt, lte, or between a {"min", "max"} value, both included. A number or null has no length and does not match. |
The native shape also accepts json_path and percentage_bucket. Flag targeting and
experiment targeting do not answer them alike, so they are not documented here.
A native condition may also carry attribute_type. Neither flag targeting nor experiment
targeting reads it: the condition is answered from the attribute's value alone.
Operator details
MATCH_REGEX (match_regex)
Tests if the value contains a match for the pattern (a search, not an anchored
match: use ^ and $ to anchor). Patterns use
RE2 syntax, which differs from
Python's re:
\w,\d,\sand\bare ASCII-only; use\p{L},\p{N}or[[:alpha:]]for other scripts.$matches only at the very end of the value, not before a final newline.- Lookahead, lookbehind, backreferences,
\Z,(?x),(?u),(?a),\N{...}, a counted repetition over 1000, and very large Unicode repetitions such as[\p{L}\p{N}]{1,300}are refused. - Values longer than 256 characters are not evaluated.
When a pattern is refused or a value cannot be evaluated, the whole ruleset is
not applied for that evaluation: a flag evaluates disabled with reason
error, a user is not eligible for the experiment (control), and a user is
not a member of the segment. python -m backend.scripts.check_regex_rules
lists stored patterns that are refused or use the ASCII-only classes or $.
A pattern RE2 refuses answers 422 when experiment or feature-flag targeting
rules are saved; python -m backend.scripts.check_targeting_rules lists the
flags whose stored rules a save would now refuse, including such patterns.
SEMANTIC_VERSION (semantic_version)
The attribute and the value are versions such as 1.2.3 or 1.2.3-beta.1; a leading v on
the attribute is ignored. An attribute that is not a whole version, such as "17.4", does not match. In the
dashboard shape a short value is filled in: 17 is 17.0.0 and 17.4 is 17.4.0. A
pre-release comes before its release (1.2.3-beta.1 is lower than 1.2.3), and eq ignores
build metadata (1.0.0+build.1 equals 1.0.0+build.2). A native condition with no
additional_value compares with eq.
GEO_DISTANCE (geo_distance)
The attribute is a point: [latitude, longitude], or an object with lat and lon (or
latitude and longitude). The condition holds when the great-circle distance from the
rule's point is at most the radius, in kilometres unless the rule gives miles or meters.
- Dashboard:
{"lat": 37.7749, "lon": -122.4194, "radius": 15, "unit": "km"}, the list[37.7749, -122.4194, 15, "km"]or the text"37.7749,-122.4194,15,km". The unit is optional. - Native:
valueis[latitude, longitude], andadditional_valueis the radius, or{"radius": 15, "unit": "km"}.
TIME_WINDOW (time_window)
Tests if a moment falls within a time window. The moment is the attribute's
value: an ISO 8601 string, a Unix timestamp or a datetime. An attribute sent
as null means the current time; an attribute that is not sent does not match.
{
"attribute": "current_time",
"operator": "time_window",
"value": {
"start": "09:00",
"end": "17:00",
"start_time": "09:00",
"end_time": "17:00",
"days": [0, 1, 2, 3, 4],
"start_date": "2024-01-01",
"end_date": "2024-01-31"
}
}
This window is open from 09:00 to 17:00, Monday to Friday, from 1 to 31 January 2024.
Keys that say when the window is open (every key given must hold):
- start_time, end_time: HH:MM or HH:MM:SS, both or neither. The end
minute is included; a start after the end spans midnight (22:00 to 02:00).
- days: day numbers, 0 = Monday to 6 = Sunday.
- start_date, end_date: YYYY-MM-DD, both or neither; both days are included.
timezone (optional), a name such as Europe/Paris, reads the moment in that
timezone. Without it, the moment's own clock time is compared.
start and end: the Condition schema requires them, but the engine
does not read them. A rule saved from the dashboard stores start and end as
HH:MM, and those are read as start_time and end_time. In the native shape,
give start_time and end_time (or the other keys) as well, as the example
does. A window with none of those keys, or with one that cannot be read (days
not 0-6, a time that is not HH:MM, an unknown timezone, a date that is not
YYYY-MM-DD), is refused, and a warning is logged once. For a user whose
evaluation reaches the window, the evaluation is abandoned: the flag answers off
for that user, with reason error, the experiment does not enrol them, and a
segment that holds the window cannot be decided for them. A user decided by an
earlier rule never reaches it, and a window in default_rule is never read. So
a window with ISO datetimes in start and end is refused, in either shape
(accepting them is #1156). For a window between two moments, use after and
before (below) in an AND group.
Flag targeting and experiment targeting read a window the same way.
BEFORE (before), AFTER (after)
before and after compare dates: an ISO 8601 string or a Unix timestamp, in the attribute
and in the value. The condition holds when the attribute is strictly before, or strictly
after, the value.
IN_SEGMENT (in_segment), NOT_IN_SEGMENT (not_in_segment)
in_segment, not_in_segment. {"attribute": "segment", "operator": "in_segment", "value": "<segment id>"}.
One segment per condition; to match any of several, put one condition per segment in an OR
group. A ruleset can use at most 10 different segments. A user is a member of an ID-list segment
when the user the flag or experiment is evaluated for (the request's user_id) is in its list,
and of a rules segment when the attributes sent with the request match its rules; in a segment's
rules, user_id is that same user, whatever the context says. Membership is decided by the
server from its own records: nothing in the context you send makes a user a member. A condition
on the attribute segment with any other operator, such as equals, compares the context value
as before. Saving rules that name a segment that is unknown, inactive or archived, or whose rules
are not valid, answers 422, and a segment condition in a native default_rule (returned without
its conditions being evaluated) is refused. When the server cannot decide a user's membership of
a segment the rules name (a flag unarchived after its segment was archived, a segment rule whose
pattern cannot be evaluated for this context), it does not guess: the flag answers
enabled: false with reason: "error" and the experiment does not enrol the user
(reason: "targeting"), whichever of the two operators the condition uses. Flags that use a
segment are always evaluated by the server, never by an SDK's local evaluation.
A segment condition requires nothing from the context. A user who lacks an attribute is not
refused outright: on experiments, as on flags, they fail only the conditions on that attribute
(is_null passes), so another OR branch can still enrol them, and they match a NOT group on
that attribute (a user with no country matches NOT (country equals US), but not
country not_equals US). Users already assigned to an experiment keep their assignment.
See Segments.
When a rule cannot be evaluated
Some conditions cannot be decided for a user: a regex pattern that is refused, or a value it
cannot evaluate; a time_window that cannot be read; a comparison with a number too large for
a floating-point number, or with a timestamp no date can hold; and a segment whose membership
the server cannot decide. The whole ruleset is abandoned for that user, so no later rule and
no default_rule applies, and a NOT around the condition does not turn it into a match:
- the flag answers
enabled: falsewithreason: "error"; - the user is not eligible for the experiment;
- a flag or an experiment that names a segment whose rules hold the condition answers as above, because the user's membership of that segment cannot be decided.
Usage examples
These are dashboard-shape rules, as the builder saves them.
Users on iOS 15 or later, on an iPhone or an iPad:
{
"logical_operator": "AND",
"groups": [
{
"logical_operator": "AND",
"conditions": [
{"attribute": "platform", "operator": "equals", "value": "iOS"},
{"attribute": "os_version", "operator": "semver_gte", "value": "15"},
{"attribute": "device_type", "operator": "in", "value": "iPhone, iPad"}
]
}
]
}
A context of {"platform": "iOS", "os_version": "16.2.0", "device_type": "iPhone"} matches.
Users within 15 km of San Francisco:
{
"groups": [
{
"conditions": [
{
"attribute": "location",
"operator": "geo_within_radius",
"value": {"lat": 37.7749, "lon": -122.4194, "radius": 15}
}
]
}
]
}
A context with "location": [37.8044, -122.2712] (Oakland, 13.4 km away) matches; with a
radius of 10 it does not.
US users over 18, or verified Canadian users:
{
"logical_operator": "OR",
"groups": [
{
"logical_operator": "AND",
"conditions": [
{"attribute": "country", "operator": "equals", "value": "US"},
{"attribute": "age", "operator": "greater_than", "value": "18"}
]
},
{
"logical_operator": "AND",
"conditions": [
{"attribute": "country", "operator": "equals", "value": "CA"},
{"attribute": "verified", "operator": "equals", "value": "true"}
]
}
]
}