# Segment filter reference (/guides/segment-filters)

One filter format shows up in three places: **dynamic segments**, **campaign audiences** with `audienceType: "FILTERED"`, and **`CONDITION` steps inside workflows**. Master it once and it carries over everywhere.

If you'd rather start with concepts than syntax, read the [Segments concept page](/concepts/segments) first.

## How a filter is structured

Every filter consists of a top-level connector (`AND` or `OR`) plus a list of **groups**. Within a group, all conditions are joined with `AND`; the top-level connector then combines the groups.

An example makes it concrete: "subscribed users **and** on the Pro plan **and** (opened **or** clicked an email recently)".

```json
{
  "logic": "AND",
  "groups": [
    {
      "filters": [
        { "field": "subscribed", "operator": "equals", "value": true },
        { "field": "data.plan",  "operator": "equals", "value": "pro" }
      ]
    },
    {
      "filters": [],
      "conditions": {
        "logic": "OR",
        "groups": [
          { "filters": [{ "field": "email.opened",  "operator": "triggeredWithin", "value": 14, "unit": "days" }] },
          { "filters": [{ "field": "email.clicked", "operator": "triggeredWithin", "value": 14, "unit": "days" }] }
        ]
      }
    }
  ]
}
```

Nesting works by giving a group its own `conditions` — that's how you express "this AND (that OR this)" without collapsing the logic into one level. When you work in the dashboard, the segment builder assembles all of this visually.

## Fields you can filter on

Each `field` value carries a namespace prefix that tells Bitelio which aspect of the contact the condition targets.

| Field pattern            | Targets                                                                             | Examples                         |
| ------------------------ | ----------------------------------------------------------------------------------- | -------------------------------- |
| `email`                  | The contact's email address                                                         | `email`                          |
| `subscribed`             | The contact's subscription state                                                    | `subscribed`                     |
| `createdAt`, `updatedAt` | Built-in contact timestamps                                                         | `createdAt`                      |
| `data.<path>`            | Custom contact data — supports nested paths                                         | `data.plan`, `data.profile.tier` |
| `event.<eventName>`      | Custom events tracked via `/v1/track`                                               | `event.signed_up`                |
| `email.<activity>`       | Email engagement: `sent`, `delivered`, `opened`, `clicked`, `bounced`, `complained` | `email.opened`                   |
| `segment.<segmentId>`    | Membership of another segment                                                       | `segment.cuid_of_other_segment`  |

## Operators

Which operators apply depends on the kind of field you're filtering.

### Text fields

For `email` and any `data.<key>` that holds a string.

| Operator      | What it does                                                       |
| ------------- | ------------------------------------------------------------------ |
| `equals`      | Exact match — ignores case on `email`, case-sensitive on `data.*`. |
| `notEquals`   | The inverse of `equals`.                                           |
| `contains`    | Substring match, ignoring case.                                    |
| `notContains` | The inverse of `contains`.                                         |

### Yes/no fields

For `subscribed` and any `data.<key>` that holds `true` or `false`.

| Operator    | What it does         |
| ----------- | -------------------- |
| `equals`    | Match true or false. |
| `notEquals` | The inverse.         |

### Dates and timestamps

For `createdAt`, `updatedAt`, and any `data.<key>` that holds an ISO 8601 date string.

| Operator                                 | What it does                                                                            |
| ---------------------------------------- | --------------------------------------------------------------------------------------- |
| `equals` / `notEquals`                   | Timestamp match. A date-only value (`YYYY-MM-DD`) matches anywhere within that UTC day. |
| `greaterThan` / `lessThan`               | Strictly after / strictly before.                                                       |
| `greaterThanOrEqual` / `lessThanOrEqual` | The inclusive versions.                                                                 |
| `within`                                 | Falls inside the last N units. Requires `unit: "days" / "hours" / "minutes"`.           |
| `olderThan`                              | Lies more than N units in the past. Requires `unit`.                                    |

On custom date fields (`data.<key>`), `within` and `olderThan` require the value to be stored as an ISO 8601 string (e.g. `"2026-05-06T12:00:00Z"`). Unix timestamps or other formats will compare incorrectly.

### Numbers

Any numeric `data.<key>` supports: `equals`, `notEquals`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`. No `unit` is involved.

### Field existence

Works on any `data.<key>`, whatever its value type.

| Operator    | What it does                                         |
| ----------- | ---------------------------------------------------- |
| `exists`    | The key is set on the contact with a non-null value. |
| `notExists` | The key is absent, or explicitly null.               |

### Events and email activity

The same operator set serves both `event.<eventName>` (custom events) and `email.<activity>` (email engagement).

| Operator             | What it does                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `triggered`          | The event has occurred for this contact at least once, at any point.                             |
| `notTriggered`       | The event has never occurred for this contact.                                                   |
| `triggeredWithin`    | Occurred at least once within the last N units. Requires `unit`.                                 |
| `triggeredOlderThan` | Has occurred, but not within the last N units. Requires `unit`.                                  |
| `notTriggeredWithin` | No occurrence in the last N units — contacts who never triggered it also match. Requires `unit`. |

### Segment membership

For `segment.<segmentId>`, where `<segmentId>` is the ID of another segment in your project.

| Operator             | What it does                                             |
| -------------------- | -------------------------------------------------------- |
| `memberOfSegment`    | The contact currently belongs to the referenced segment. |
| `notMemberOfSegment` | The contact does not belong to it.                       |

## Quick reference: what to pass with each operator

* **Take a `value`**: `equals`, `notEquals`, `contains`, `notContains`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `within`, `olderThan`, `triggeredWithin`, `triggeredOlderThan`, `notTriggeredWithin`.
* **Take a `unit`** (`"days"`, `"hours"`, or `"minutes"`): `within`, `olderThan`, `triggeredWithin`, `triggeredOlderThan`, `notTriggeredWithin`.
* **Take nothing else**: `exists`, `notExists`, `triggered`, `notTriggered`, `memberOfSegment`, `notMemberOfSegment`.

## Examples

### Engaged users on the Pro plan

Subscribed Pro-plan users who opened or clicked any email in the last 14 days but haven't clicked anything in the last 30 days.

```json
{
  "logic": "AND",
  "groups": [
    {
      "filters": [
        { "field": "subscribed", "operator": "equals", "value": true },
        { "field": "data.plan",  "operator": "equals", "value": "pro" }
      ]
    },
    {
      "filters": [],
      "conditions": {
        "logic": "OR",
        "groups": [
          { "filters": [{ "field": "email.opened",  "operator": "triggeredWithin", "value": 14, "unit": "days" }] },
          { "filters": [{ "field": "email.clicked", "operator": "triggeredWithin", "value": 14, "unit": "days" }] }
        ]
      }
    },
    {
      "filters": [
        { "field": "email.clicked", "operator": "notTriggeredWithin", "value": 30, "unit": "days" }
      ]
    }
  ]
}
```

### New trial users without a purchase

Subscribed contacts created in the last 7 days who haven't yet triggered a `purchase` event.

```json
{
  "logic": "AND",
  "groups": [
    {
      "filters": [
        { "field": "subscribed",     "operator": "equals",       "value": true },
        { "field": "createdAt",      "operator": "within",       "value": 7, "unit": "days" },
        { "field": "event.purchase", "operator": "notTriggered" }
      ]
    }
  ]
}
```

### Power users above a tier

Members of the `power-users` segment whose lifetime value is at least 500.

```json
{
  "logic": "AND",
  "groups": [
    {
      "filters": [
        { "field": "segment.<powerUsersSegmentId>", "operator": "memberOfSegment" },
        { "field": "data.lifetimeValue", "operator": "greaterThanOrEqual", "value": 500 }
      ]
    }
  ]
}
```

## Related

<Cards>
  <Card title="Segments" href="/concepts/segments">
    Static vs dynamic segments, membership tracking, and entry / exit events.
  </Card>

  <Card title="Custom fields" href="/guides/custom-fields">
    How `data.*` fields are typed and used in filters.
  </Card>
</Cards>
