# Condition types

> Every segment condition, the fields and operators it accepts, and its limits.

Source: https://docs.grovs.io/docs/analytics/segments/condition-types

Each condition has a `type` plus fields that depend on it. The examples show each condition in a segment definition's JSON form.

| Dashboard            | `type`        | Matches                                             |
| -------------------- | ------------- | --------------------------------------------------- |
| **Did an event**     | `event`       | A built-in event, a custom event, or any activity   |
| **Device**           | `device`      | Device and app fields                               |
| **Profile property** | `profile`     | A property your app set on the user                 |
| **First seen**       | `first_seen`  | When the person first appeared                      |
| **Identifier**       | `identifier`  | Your own user ID, or an uploaded list               |
| **Acquired via**     | `acquisition` | The install or reinstall that brought the person in |
| **Purchase**         | `purchase`    | In-app purchases and subscriptions                  |

## Shared rules

- **Values.** 1 to 100 per condition, each a non-empty string of up to 256 bytes.
- **Operators on text.** `is` and `is_not` compare exact values. `contains` is a case-insensitive substring match.
- **Windows.** See [Time windows](https://docs.grovs.io/docs/analytics/segments#time-windows). A window is `{"last_days": N}` or `{"from": "YYYY-MM-DD", "to": "YYYY-MM-DD"}`.
- **Negative conditions.** Those marked "negative" below must sit in an `and` group with a positive condition.

## Did an event

Matches people who raised an event at least `count` times in the window.

```json
{ "type": "event", "event_name": "checkout",
  "filters": [{ "field": "properties.plan", "op": "is", "value": "pro" }],
  "count": 3, "window": { "last_days": 30 } }
```

| Field        | Required     | Notes                                                                                                                                        |
| ------------ | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_type` | one of three | A built-in event: `app_open`, `view`, `open`, `install`, `reinstall`, `time_spent`, `reactivation`, `user_referred`, `screen_view`, `custom` |
| `event_name` | one of three | A custom event name, up to 256 bytes                                                                                                         |
| `any`        | one of three | `true` matches any activity                                                                                                                  |
| `count`      | no           | At least this many times. Default `1`                                                                                                        |
| `window`     | yes          | Up to 90 days                                                                                                                                |
| `filters`    | no           | Up to 25. Each has `field`, `op` (`is`, `is_not`, `contains`) and `value`                                                                    |
| `negate`     | no           | `true` means "did not". Negative                                                                                                             |

Give exactly one of `event_type`, `event_name` or `any`.

Filter fields: `screen_name`, `platform`, `app_version`, `country`, `city`, `device_model`, `os`, `os_version`, `language`, `tracking_source`, `tracking_medium`, `tracking_campaign`, `ads_platform`, or an event property as `properties.<key>`. In the dashboard, click **where** on the condition to add one.

Message events, such as `push_open` and `in_app_viewed`, do not count toward **Any activity** or **Any custom event**, because a message shown is not something the person did. Target a message event by its name instead.

## Device

Matches on a device or app field.

```json
{ "type": "device", "field": "platform", "op": "is", "value": ["ios"], "mode": "current" }
```

| Field    | Required   | Notes                                                                                |
| -------- | ---------- | ------------------------------------------------------------------------------------ |
| `field`  | yes        | `platform`, `os`, `os_version`, `app_version`, `device_model`, `language`, `country` |
| `op`     | yes        | `is`, `is_not`, `contains`                                                           |
| `value`  | yes        | 1 to 100 values                                                                      |
| `mode`   | no         | `current` (default) or `used`                                                        |
| `window` | for `used` | Up to 90 days                                                                        |

The two modes:

- ****current device**** (`current`): the latest value seen in the last 30 days. No window. `is_not` means "has a known current value that is not this one", so it counts as positive.
- ****any device used**** (`used`): any event with that value in the window. `is_not` means "not seen with that value in the window", so people with no activity in the window also match. This `is_not` is negative.

On self-hosted deployments, `SEGMENT_DEVICE_CURRENT_DAYS` sets the 30-day lookback of `current`.

## Profile property

Matches the **current** value of a profile property your app set through the SDK, not its value at the time of an event.

```json
{ "type": "profile", "field": "properties.plan", "op": "is", "value": ["pro"] }
```

| Field   | Required                       | Notes                                              |
| ------- | ------------------------------ | -------------------------------------------------- |
| `field` | yes                            | `properties.<key>`                                 |
| `op`    | yes                            | `is`, `is_not`, `contains`, `is_set`, `is_not_set` |
| `value` | not for `is_set`, `is_not_set` | 1 to 100 values                                    |

`is_not` and `is_not_set` are negative. A person with no profile also matches `is_not`.

A dotted key such as `user.plan` and the nested path `user` › `plan` are the same property.

## First seen

Matches the date of the person's earliest built-in event.

```json
{ "type": "first_seen", "op": "gte", "value": "2026-01-01" }
```

| Field   | Required | Notes                                               |
| ------- | -------- | --------------------------------------------------- |
| `op`    | yes      | `gte` (**on or after**) or `lte` (**on or before**) |
| `value` | yes      | An ISO date, `YYYY-MM-DD`                           |

Only built-in events set first seen. A person whose only activity is custom events never matches.

## Identifier

Matches the user identifier your app set through the SDK.

```json
{ "type": "identifier", "op": "is", "value": ["member-4471"] }
```

| `op`          | Value           | Meaning                                              |
| ------------- | --------------- | ---------------------------------------------------- |
| `is_set`      | none            | The person has an identifier                         |
| `is_not_set`  | none            | The person has no identifier. Negative               |
| `is`          | 1 to 100 values | Exact match                                          |
| `contains`    | 1 to 100 values | Case-insensitive substring                           |
| `in_list`     | none            | The identifier is in the segment's uploaded list     |
| `not_in_list` | none            | The identifier is not in the uploaded list. Negative |

`in_list` and `not_in_list` only work on a saved segment that has an uploaded list. See [Identifier lists](https://docs.grovs.io/docs/analytics/segments/identifier-lists).

## Acquired via

Matches people who had an install or reinstall from a link, campaign, source or medium in the window.

```json
{ "type": "acquisition", "field": "campaign_id", "op": "is", "value": [41], "window": { "last_days": 365 } }
```

| Field    | Required | Notes                                                                                            |
| -------- | -------- | ------------------------------------------------------------------------------------------------ |
| `field`  | yes      | `link_id`, `campaign_id`, `tracking_source`, `tracking_medium`                                   |
| `op`     | yes      | `is` or `is_not` for `link_id` and `campaign_id`. `is`, `is_not` or `contains` for the other two |
| `value`  | yes      | Positive integer IDs for `link_id` and `campaign_id`, text for the others. 1 to 100 values       |
| `window` | no       | Up to 365 days. Without one, Grovs uses 365 days, or your retention if it is shorter             |

`is_not` is negative.

## Purchase

Matches people with at least `count` purchases in the window, optionally above a spending total.

```json
{ "type": "purchase", "product_id": ["pro_yearly"], "purchase_type": ["subscription"],
  "count": 1, "min_total_usd_cents": 999, "window": { "last_days": 90 } }
```

| Field                 | Required | Notes                                                                             |
| --------------------- | -------- | --------------------------------------------------------------------------------- |
| `product_id`          | no       | Product IDs. Omit for any product                                                 |
| `purchase_type`       | no       | `subscription`, `one_time`                                                        |
| `store_source`        | no       | The store. In the dashboard: **on the App Store**, **on Google Play**             |
| `count`               | no       | At least this many purchases. Default `1`                                         |
| `min_total_usd_cents` | no       | Minimum total spend in US cents. Default `0`. In the dashboard: **totalling ≥ $** |
| `window`              | yes      | Up to 365 days, on the purchase date                                              |
| `negate`              | no       | `true` means "did not buy". Negative                                              |

How purchases count:

- One transaction counts once, from its `buy`. Renewals and repeat purchases are separate transactions.
- A refunded transaction does not count, unless the refund was later reversed.
- `quantity` raises the spend total. It does not multiply the count.
- Totals use the purchase's US dollar value.

Saving a segment with a purchase condition fails while [revenue tracking](https://docs.grovs.io/docs/revenue) is off.

## Limits

| Limit                            | Value     |
| -------------------------------- | --------- |
| Groups per definition            | 5         |
| Conditions per group             | 10        |
| Filters per event condition      | 25        |
| Values per condition or filter   | 100       |
| Value length                     | 256 bytes |
| Identifiers per uploaded list    | 200,000   |
| Event and device windows         | 90 days   |
| Acquisition and purchase windows | 365 days  |

Self-hosted deployments can change the window caps with environment variables. The other limits are fixed.
