Segment Operators
All 12 operators supported by the Amba segment rule evaluator, with examples of the fields each one works on.
Segment rules live in packages/shared/src/segment-rules.ts. This page lists every operator, the fields it's valid against, and the exact SQL that gets emitted. All identifiers are whitelisted — admin-authored rules cannot inject arbitrary SQL.
Operator reference
| Operator | Valid on | Example |
|---|---|---|
eq | any | properties.plan eq "premium" |
neq | any | properties.plan neq "free" |
gt | numeric / timestamp | properties.total_xp gt 1000 |
gte | numeric / timestamp | properties.streak_count gte 7 |
lt | numeric / timestamp | properties.age lt 25 |
lte | numeric / timestamp | properties.age lte 25 |
contains | string / text-castable field | email contains "@example.com" |
not_contains | string / text-castable field | email not_contains "spam" |
exists | any | email exists |
not_exists | any | email not_exists |
within | date column only | last_seen_at within "7d" |
not_within | date column only | last_seen_at not_within "30d" |
Field domains
The evaluator resolves field into one of four target surfaces. Anything outside the whitelists silently falls back to FALSE (the condition never matches) rather than raising — an admin typo in a rule won't take the workflow down.
Direct columns on app_users
Whitelisted: id, external_id, anonymous_id, email, phone, display_name, first_seen_at, last_seen_at, created_at.
JSONB properties (properties.*)
Any path matching [a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*. Compiled via properties #>> $path::text[], so nested keys are safe:
within / not_within are not supported on property fields — promote the value to a typed column if you need time-based filtering.
Entitlements (entitlements.*)
Whitelisted columns: entitlement_id, product_id, is_active, store, period_type, purchase_date, expiration_date.
The evaluator emits EXISTS (SELECT 1 FROM user_entitlements ue WHERE ue.app_user_id = app_users.id AND ...) so a user who has any entitlement row matching the clause qualifies.
Unknown fields
Return NULL in the SQL, which makes every comparison falsy. No error is raised — the condition simply never matches. This keeps a misconfigured rule from taking the whole segment evaluation down.
Duration literals (within / not_within)
Values are parsed as <number><unit>:
| Unit | Meaning |
|---|---|
m | minutes |
h | hours |
d | days |
w | weeks |
Examples: "15m", "24h", "7d", "2w". Invalid literals (bad unit, non-numeric) fall back to FALSE.
within emits >= NOW() - INTERVAL 'N unit' and not_within emits < NOW() - INTERVAL 'N unit'.
contains / not_contains
Implemented as column::text ILIKE '%value%'. Case-insensitive by default. The value must be a string — numeric value on a contains clause returns FALSE.
On entitlement fields the same ILIKE check runs inside EXISTS (SELECT 1 FROM user_entitlements ...).
exists / not_exists
On direct columns: column IS NOT NULL / IS NULL.
On property paths: properties #> $path::text[] IS NOT NULL / IS NULL — works for nested keys.
On entitlements: EXISTS (SELECT 1 FROM user_entitlements ue WHERE ue.app_user_id = app_users.id AND ue.column IS NOT NULL) — matches when a user has any row with that column populated.
Composing rules
Only top-level operator (AND / OR) is supported today. No nesting. For disjoint branches, create multiple segments and combine them at targeting time (e.g. include multiple segment_ids in a campaign — coming soon).
Evaluation cadence
Every 15 minutes, SegmentEvaluationAllProjectsWorkflow enumerates every project and per-project recomputes segment_memberships for every non-system segment. Manual POST /admin/segments/:id/evaluate triggers a one-off immediate run against that segment only.
Next
- Quickstart — create + target your first segment.
- Remote config — segment-gated feature flags.
- Push campaigns — segment-targeted pushes.