Segments

A segment is a reusable audience — a saved query over user properties and behaviour. Every targeted channel (feedback, surveys, in-app, push, requests) uses this same builder, the same row UI, and the same DSL.

One server-authoritative DSL

The audience you build here is stored once and evaluated server-side. Eligible feedback, survey, and in-app work is written to a durable per-subject instruction inbox; every production SDK polls and acknowledges that inbox. The shared pure evaluator remains available for previews and contract tests, not as a second source of truth.

How conditions compose

A segment is a flat list of rows under one Match selector:

SelectorUI textMeaning
combinator: 'all'All of these criteriaAND — every row must match.
combinator: 'any'Any of these criteriaOR — any row matches.

Click Add criteria to add rows. Each row is Field · Operator · Value. The dashboard supports a flat list today (no nested groups in the UI).

The pillar (feedback / survey / in-app) builders use the same row but add a top-level Everybody vs Send only to radio — segments skip that since "everyone" is just the absence of any criteria.

The 8 condition kinds

Field labels and hints match the dashboard verbatim.


1. Segment — "Pick from saved segments"

Reuse a saved segment as a row inside another segment.

Operator valueUI label
isis matching
is_notis not matching

Value: a list of segment IDs (multi-select from your saved segments).

{ "kind": "segment", "op": "is", "segmentIds": ["seg_paying_users"] }

2. Country — "Match by ISO country code"

ISO 3166-1 alpha-2 codes.

Operator valueUI label
is_any_ofis any of
is_none_ofis none of

Value: list of country codes.

{ "kind": "country", "op": "is_any_of", "values": ["DE","FR","IT","ES","NL","BE","SE","DK","FI"] }

3. Last active — "When the user was last seen"

Recency on activity.

Operator valueUI labelValue
within_dayswithin lastdays
before_daysbefore lastdays
nevernever active—
{ "kind": "last_active", "op": "before_days", "days": 14 }

4. Install date — "When the user first appeared"

Cohort by first-seen timestamp.

Operator valueUI labelValue
within_dayswithin lastdays
before_daysbefore lastdays
after_dateafterISO date
before_datebeforeISO date
betweenbetweenfrom + to
{ "kind": "install_date", "op": "within_days", "days": 7 }

5. Platform — "iOS / Android / Web"

Operator valueUI label
isis
is_notis not

Value: list of one or more platforms (ios / android / web).

{ "kind": "platform", "op": "is", "values": ["ios"] }

6. User property — "Custom user attribute"

The most flexible row. Match against anything you've sent via identify. The UI exposes 8 operators:

Operator valueUI label
eqequals
neqnot equals
inis one of
ninis not one of
containscontains
starts_withstarts with
existsexists
not_existsdoes not exist
{ "kind": "user_property", "key": "plan", "op": "in", "value": ["pro", "enterprise"] }
Numeric comparisons exist in the schema but not the UI

The API also accepts gt, gte, lt, lte on user_property for numeric or date fields. They aren't yet exposed in the dropdown — use the REST API directly if you need them on a segment.


7. Performed event — "Has / has not done X"

Behavioural matcher.

Operator valueUI label
didhas performed
did_nothas not performed

Plus fields:

  • eventName — autocomplete from registered events.
  • windowDays — 1–365. Defaults to 30.
  • minCount (optional, for did) — only count if the user did it at least N times.
{
  "kind": "event_performed",
  "op": "did",
  "eventName": "session_started",
  "windowDays": 7,
  "minCount": 5
}
{
  "kind": "event_performed",
  "op": "did_not",
  "eventName": "subscription_canceled",
  "windowDays": 365
}

8. App version — "What version of your app the user is on"

Semver-aware matching against the SDK's reported appVersion.

Operator valueUI label
eqequals
neqnot equals
gteat least
lteat most
gtgreater than
ltless than
{ "kind": "app_version", "op": "gte", "version": "2.4.0" }

Reserved properties (auto-attached)

Every event ingest carries an SDK-attached context. These keys are queryable from user_property rows without you having to set them.

KeyTypeSource
anonymousIdstringGenerated locally by the SDK.
externalIdstringSet on identify.
sdkVersionstringThe SDK build.
platformios · android · react-native · flutter · webSDK runtime.
appVersionstringYour app's CFBundleShortVersionString / versionName.
localestringe.g. en-DE.
timezonestringIANA — Europe/Berlin.
osName / osVersionstringiOS / Android version.
deviceModelstringMarketing model name.
countrystringDerived at ingest from locale + IP enrichment.

Preview & audience size

As you edit, an audience badge updates with the current size + a small user sample. The preview hits POST /v1/apps/:appId/audience/preview and computes from ClickHouse in a single query.

Segment builder with a United States country criterion, matching audience count, and sample members.
Preview the matching audience as you prepare criteria. This example has not been saved.

Storage model

The DSL on disk, verbatim from @usergist/sdk-core:

interface AudienceSpec {
  version: 1
  mode: 'everyone' | 'match'
  combinator: 'all' | 'any'
  conditions: ReadonlyArray<AudienceCondition>
}
 
type AudienceCondition =
  | SegmentCondition
  | CountryCondition
  | LastActiveCondition
  | InstallDateCondition
  | PlatformCondition
  | UserPropertyCondition
  | EventPerformedCondition
  | AppVersionCondition

evaluateSegment(dsl, user, now) is exported from @usergist/sdk-core for server logic, previews, and parity tests. Production delivery eligibility remains server-authoritative.

Worked examples

Power users

{
  "version": 1,
  "mode": "match",
  "combinator": "all",
  "conditions": [
    { "kind": "user_property", "key": "plan", "op": "in", "value": ["pro", "enterprise"] },
    { "kind": "event_performed", "op": "did", "eventName": "session_started", "windowDays": 7, "minCount": 5 }
  ]
}

Trial day 3

{
  "version": 1,
  "mode": "match",
  "combinator": "all",
  "conditions": [
    { "kind": "install_date", "op": "within_days", "days": 3 },
    { "kind": "install_date", "op": "before_days", "days": 2 }
  ]
}

Churn risk

{
  "version": 1,
  "mode": "match",
  "combinator": "all",
  "conditions": [
    { "kind": "user_property", "key": "plan", "op": "in", "value": ["pro", "enterprise"] },
    { "kind": "last_active", "op": "before_days", "days": 14 },
    { "kind": "event_performed", "op": "did_not", "eventName": "subscription_canceled", "windowDays": 365 }
  ]
}

More: Guides → Targeting recipes.

API

  • GET /v1/apps/:appId/segments — list
  • POST /v1/apps/:appId/segments — create
  • PATCH /v1/apps/:appId/segments/:segmentId — update
  • POST /v1/apps/:appId/audience/preview — size + sample

Canonical schemas: apps/api/src/schemas/segments.ts. Evaluator: packages/sdk-core/src/evaluate/segment.ts.

What's next