Targeting decides who gets which variation. You pass a context with every evaluation, and rules match against its attributes.
The evaluation context
context.ts
const ctx = {
user: {
id: "u_42", // required, used for stable bucketing
email: "mia@acme.dev",
plan: "pro",
country: "KR",
createdAt: "2026-03-14",
},
device: { os: "ios", appVersion: "5.2.0" },
};Any attribute can be targeted. Nested objects are addressed with dots, e.g. device.appVersion.
Rules
A rule is a list of conditions (all must match) and a result — a variation or a percentage rollout.
rule.json
{
"description": "Pro users in Korea",
"conditions": [
{ "attribute": "user.plan", "op": "in", "values": ["pro", "team"] },
{ "attribute": "user.country", "op": "eq", "values": ["KR"] },
{ "attribute": "device.appVersion", "op": "semver_gte", "values": ["5.0.0"] }
],
"serve": { "variation": "on" }
}Operators
eq,neq,in,not_in— exact matchescontains,starts_with,ends_with,matches(regex) — stringsgt,gte,lt,lte— numbers and ISO datessemver_eq,semver_gte,semver_lt— version stringsin_segment— membership in a saved segment
Segments
A segment is a reusable rule — “Beta testers”, “Internal staff”, “Enterprise accounts”. Update the segment once and every flag that targets it follows.
Percentage rollouts
Instead of a single variation, a rule can split traffic. Shoal hashes flagKey + user.id so the same user always lands in the same bucket, across servers and sessions.