# Event ingestion filtering - Docs

Event ingestion filtering lets you drop events at ingestion time based on event metadata. Filters are evaluated early in the [ingestion pipeline](/docs/how-posthog-works/ingestion-pipeline.md), before transformations run, making it the most efficient way to exclude unwanted events from your data.

Common use cases include:

-   Dropping events from automated bots or internal tools
-   Filtering out test or staging traffic by `distinct_id`
-   Removing high-volume, low-value events to reduce costs
-   Temporarily filtering buggy data while you roll out a client-side fix

You can find it at [**Data management** > **Event filtering**](https://app.posthog.com/data-management/event-filtering).

## How it works

Each incoming event is evaluated against a set of conditions you define. If an event matches the filter, PostHog drops it before ingestion. If it doesn't match, it continues through the pipeline as normal.

Filters run **before** [transformations](/docs/cdp/transformations.md) and [destinations](/docs/cdp/destinations.md), so dropped events don't trigger any downstream processing.

If your filtering logic can be expressed here, we recommend using event ingestion filtering over a [drop events transformation](/docs/cdp/transformations/drop-events.md). It runs earlier in the pipeline and doesn't require writing custom code.

## Operating modes

The filter has three modes:

| Mode | Behavior |
| --- | --- |
| Disabled | No filtering is applied. All events are ingested normally. |
| Dry run | Matching events are counted but not dropped. Use this to validate your filter before going live. |
| Live | Matching events are dropped from ingestion. |

We recommend starting with **dry run** mode and checking the metrics to confirm your filter is matching the right events before switching to **live**.

## Creating a filter

Filters are built using a visual expression builder that supports nested boolean logic (AND, OR, NOT).

Each condition has three parts:

1.  **Field** – what to match on:

    -   `event_name` – the name of the event (e.g., `$pageview`, `signup`)
    -   `distinct_id` – the [distinct ID](/docs/data/persons.md) attached to the event
2.  **Operator** – how to match:

    -   `equals` – the field value matches the specified value exactly
    -   `contains` – the field value contains the specified string
3.  **Value** – the string to match against

### Combining conditions

-   **AND groups** match when **all** conditions in the group are true.
-   **OR groups** match when **any** condition in the group is true.
-   **NOT** negates a group, matching when the group's conditions are **not** true.

You can nest groups up to five levels deep and add up to 20 conditions total. Conditions and groups can be reordered using drag and drop.

**Example:** To drop all `$pageview` events from a bot with distinct ID containing `bot-crawler`:

PostHog AI

```
AND
├── event_name equals "$pageview"
└── distinct_id contains "bot-crawler"
```

**Example:** To drop events from multiple internal tools:

PostHog AI

```
OR
├── distinct_id contains "internal-tool-a"
└── distinct_id contains "internal-tool-b"
```

## Test cases

Events dropped by a filter can't be recovered, so verify your filter works correctly before turning it on. Test cases help you do this – they validate the filter against example events and serve as living documentation of what the filter is intended to match.

Each test case specifies:

-   An **event name**
-   A **distinct ID**
-   The **expected result** – either "Drop" or "Ingest"

Test cases run locally in your browser and show a pass/fail result. The filter can't be enabled until all tests pass – if you try to save in **live** mode with failing tests, it automatically downgrades to **dry run**.

## Metrics

The event filtering page shows approximate counts of:

-   **Dropped** – events the filter dropped in live mode
-   **Would be dropped** – events the filter matched in dry run mode (not actually dropped)

Use these metrics to validate your filter is matching the right volume and type of events before going live.

> **Note:** Metrics are processed on a best-effort basis and may be off by a small percentage.

## Limits

-   Maximum **20 conditions** per filter
-   Maximum **five levels** of nesting
-   Only `event_name` and `distinct_id` fields are supported
-   Only `equals` and `contains` operators are supported
-   One filter configuration per project

## Further reading

-   [How the ingestion pipeline works](/docs/how-posthog-works/ingestion-pipeline.md)
-   [Ingestion warnings](/docs/data/ingestion-warnings.md)
-   [Transformations](/docs/cdp/transformations.md)

### Community questions

Ask a question

### Was this page useful?

HelpfulCould be better