> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Audience targeting

> Audience filters decide whether a user qualifies for a placement or experiment, based on attributes your app sends to the SDK.

An **audience filter** decides whether a given user qualifies for a placement (or an experiment). It is evaluated at resolve time against the **attributes** your app sends to the SDK. If the user matches, they get the flow; if not, the placement returns nothing.

Targeting is only as good as the attributes you send. The backend only sees what the SDK puts in the resolve request, so the contract between your dashboard filter and your app's attributes has to line up exactly.

## The filter shape

A filter has a **match mode** and a list of **conditions**:

```json theme={null}
{
  "match": "all",
  "conditions": [
    { "property": "plan", "operator": "equals", "value": "premium" },
    { "property": "sessions_count", "operator": "gte", "value": "5" }
  ]
}
```

* **`match`** is `all` (every condition must be true, an AND) or `any` (at least one condition must be true, an OR). In the audience builder this is the "Show flow when ALL / ANY conditions match" toggle.
* Each **condition** is a `property` (the attribute key), an `operator`, and a `value`.

**No filter means everyone matches.** A placement with no audience filter serves its flow to every user who passes the platform check.

## Operators

The resolution engine supports the operators below. The dashboard's audience builder currently lets you pick the first group (equality and numeric); the others are evaluated by the engine and are useful to know when you inspect a stored filter or manage placements through the API.

### Selectable in the audience builder

| Operator     | Meaning                             | Notes                                |
| ------------ | ----------------------------------- | ------------------------------------ |
| `equals`     | Value matches exactly               | Compared as strings. Case-sensitive. |
| `not_equals` | Value does not match                |                                      |
| `contains`   | Attribute string contains the value | Case-insensitive.                    |
| `gt`         | Greater than                        | Numeric comparison.                  |
| `lt`         | Less than                           | Numeric comparison.                  |
| `gte`        | Greater than or equal               | Numeric comparison.                  |
| `lte`        | Less than or equal                  | Numeric comparison.                  |

### Also supported by the engine

| Operator      | Meaning                       | Notes                                                 |
| ------------- | ----------------------------- | ----------------------------------------------------- |
| `in`          | Attribute is in a list        | Value is an array.                                    |
| `not_in`      | Attribute is not in a list    | Value is an array.                                    |
| `exists`      | The attribute is present      | Value is ignored.                                     |
| `not_exists`  | The attribute is absent       | Value is ignored.                                     |
| `starts_with` | String starts with the value  | Case-insensitive.                                     |
| `ends_with`   | String ends with the value    | Case-insensitive.                                     |
| `regex`       | Glob-style pattern match      | Only `*` is a wildcard. Not full regular expressions. |
| `version_gt`  | Version greater than          | Dotted version compare (`3.2.0`).                     |
| `version_lt`  | Version less than             | Dotted version compare.                               |
| `version_gte` | Version greater than or equal | Dotted version compare.                               |
| `version_lte` | Version less than or equal    | Dotted version compare.                               |

<Note>
  The `version_*` operators parse versions part by part (`3.10.0` is greater than `3.9.0`) and strip non-numeric suffixes, so `1.0.0-beta` is treated as `1.0.0`.
</Note>

## Properties (the attributes contract)

A condition's `property` is a key the SDK includes in the resolve request's `attributes` map. Only what the SDK sends is reachable. The dashboard's audience builder suggests a curated set of properties:

| Property         | Label          | Where it comes from                                          |
| ---------------- | -------------- | ------------------------------------------------------------ |
| `app.version`    | App Version    | Reported automatically by the iOS SDK (from the app bundle). |
| `app.build`      | App Build      | Reported automatically by the iOS SDK (from the app bundle). |
| `plan`           | Plan           | You must set it (pass it in the SDK context).                |
| `user_type`      | User Type      | You must set it.                                             |
| `sessions_count` | Sessions Count | You must set it.                                             |
| `is_verified`    | Verified User  | You must set it.                                             |

The first two are populated for you by the **iOS** SDK (from the app bundle). The **Expo** SDK does not set `app.version` or `app.build` automatically; if you want to target on app version there, pass your own key (for example `app_version`) in the SDK context. The rest are custom: your app supplies them. You are not limited to this list, any key you put in the SDK context can be targeted, but the dashboard only suggests these.

### Setting attributes from the SDK

Attributes that you control are set through the SDK **context**. Both SDKs accept an initial `context` at configuration time; they differ in how you change it afterward.

<Tabs>
  <Tab title="iOS">
    ```swift theme={null}
    // At configuration time
    let config = FlowPilotConfiguration(
        apiKey: "fp_live_xxx",
        appId: "your-app-id",
        context: [
            "plan": "premium",
            "sessions_count": 5
        ]
    )
    FlowPilot.configure(config)

    // Or update later, before you present the placement
    FlowPilot.shared?.updateContext([
        "plan": "premium",
        "sessions_count": 5
    ])
    ```

    On iOS, the SDK also adds device and app attributes automatically (`app.version`, `app.build`, and device info). See [Variables and SDK context (iOS)](/ios-sdk/variables-and-context) for the full list.
  </Tab>

  <Tab title="Expo">
    ```tsx theme={null}
    // At configuration time
    FlowPilot.configure({
      apiKey: 'fp_live_xxx',
      appId: 'your-app-id',
      context: {
        plan: 'premium',
        sessions_count: 5,
      },
    });

    // The Expo SDK has no public updateContext. To change attributes
    // after login, call configure(...) again with the new context
    // before you present the next placement.
    FlowPilot.configure({
      apiKey: 'fp_live_xxx',
      appId: 'your-app-id',
      context: { plan: 'premium', sessions_count: 5 },
    });
    ```

    The Expo SDK sends the device platform (`ios` / `android`) with every resolve, but does not auto-populate `app.version` / `app.build`. See [Variables and context (Expo)](/expo-sdk/variables-and-context) for how context flows into a flow.
  </Tab>
</Tabs>

<Warning>
  **Dot-notation property names are read as nested paths.** The engine treats a property like `device.platform` as a lookup into a nested object (`attributes["device"]["platform"]`). The iOS SDK reports `app.version` and `app.build` as flat keys whose name happens to contain a dot, not as nested objects, so a filter on those dotted names may not match as expected. For reliable targeting, prefer flat custom keys you set yourself (for example a `plan` or `app_version` key in your context).
</Warning>

## Example: premium users with enough sessions

This filter targets users on the `premium` plan who have opened the app at least five times.

**Dashboard side** (the stored `audience_filter`):

```json theme={null}
{
  "match": "all",
  "conditions": [
    { "property": "plan", "operator": "equals", "value": "premium" },
    { "property": "sessions_count", "operator": "gte", "value": "5" }
  ]
}
```

In the builder you would set "Show flow when **ALL** conditions match", then add **Plan** `equals` `premium` and **Sessions Count** `greater or equal` `5`.

**App side** (the matching attributes your iOS app sends):

```swift theme={null}
FlowPilot.shared?.updateContext([
    "plan": "premium",
    "sessions_count": 5
])

// Later, at the trigger point:
try await FlowPilot.shared?.presentPlacement("paywall", from: viewController)
```

The keys in your context (`plan`, `sessions_count`) must match the `property` names in the filter exactly. Numeric operators like `gte` work whether the value arrives as a number or a numeric string, so `5` and `"5"` both compare correctly.

## Common mistakes

* **Property name mismatch.** The filter property and the attribute key must be identical. A filter on `plan` will never match an attribute named `subscription_plan`.
* **Expecting server-side enrichment.** The backend only evaluates what the SDK sends. It does not look up user records or enrich attributes. If you do not send `is_verified`, a filter on it cannot match (use `exists` carefully, an absent attribute is treated as not present).
* **Treating `regex` as full regex.** The `regex` operator is glob-style: only `*` is a wildcard. A PCRE pattern like `^prem.*$` will not behave as you expect.
* **Case assumptions.** `equals` is case-sensitive, but `contains`, `starts_with`, and `ends_with` are case-insensitive. Normalize values you care about.
* **Dotted property names.** See the warning above. Use flat custom keys for targeting you depend on.

## Related pages

* [Creating a placement](/dashboard/creating-placements)
* [Creating an experiment](/dashboard/creating-experiments)
* [Variables and SDK context (iOS)](/ios-sdk/variables-and-context)
* [SDK REST API](/reference/sdk-rest-api)
