Amba

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

OperatorValid onExample
eqanyproperties.plan eq "premium"
neqanyproperties.plan neq "free"
gtnumeric / timestampproperties.total_xp gt 1000
gtenumeric / timestampproperties.streak_count gte 7
ltnumeric / timestampproperties.age lt 25
ltenumeric / timestampproperties.age lte 25
containsstring / text-castable fieldemail contains "@example.com"
not_containsstring / text-castable fieldemail not_contains "spam"
existsanyemail exists
not_existsanyemail not_exists
withindate column onlylast_seen_at within "7d"
not_withindate column onlylast_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.

{ "field": "email", "op": "exists" }
{ "field": "last_seen_at", "op": "within", "value": "14d" }

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:

{ "field": "properties.plan", "op": "eq", "value": "premium" }
{ "field": "properties.user.level", "op": "gte", "value": 10 }

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.

{ "field": "entitlements.is_active", "op": "eq", "value": true }
{ "field": "entitlements.product_id", "op": "eq", "value": "premium_monthly" }

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>:

UnitMeaning
mminutes
hhours
ddays
wweeks

Examples: "15m", "24h", "7d", "2w". Invalid literals (bad unit, non-numeric) fall back to FALSE.

{ "field": "last_seen_at", "op": "within", "value": "7d" }
{ "field": "created_at", "op": "not_within", "value": "30d" }

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.

{ "field": "properties.tags", "op": "contains", "value": "fitness" }
{ "field": "email", "op": "not_contains", "value": "@example.com" }

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).

{
  "operator": "AND",
  "conditions": [
    { "field": "last_seen_at", "op": "within", "value": "7d" },
    { "field": "entitlements.is_active", "op": "eq", "value": true },
    { "field": "properties.streak_count", "op": "gte", "value": 5 }
  ]
}

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

On this page