# Filtering Rules

> Filter and route notifications per webhook and channel by priority, tags, title, message, or JSON payload fields - operators, AND/OR groups, regex, and testing filters.

Source: https://docs.alphorn.dev/filtering/

Filtering rules let you control which notifications reach which channels. Instead of sending everything everywhere, route messages based on priority, tags, or payload content.

## How Filtering Works

Filters are attached to the link between a **webhook** and a **channel**. The same channel can have a filter on one webhook and none on another.

When a notification arrives, Alphorn evaluates the filter of each channel linked to the webhook:

- **Match** - The notification is delivered to that channel
- **No match** - The notification is skipped for that channel
- **No filter** - All notifications are delivered (default behavior)

Filters are evaluated once, when the notification arrives. Changing a filter does not affect notifications that were already received.

## Setting Up a Filter

1. Create a webhook, or open an existing one and click **Edit** on the **Output Channels** card
2. Select a channel, expand it, and click **Add filter**
3. Pick a field, an operator, and a value
4. Use **Add condition** to add more conditions to the group, and **Add OR group** for alternatives
5. Save - **Save channels** on the webhook page, or **Save changes** in the edit form

The **Clear filter** button removes the filter, so the channel receives everything again.

## Combining Conditions

Conditions are organized in groups:

- All conditions **within a group** must match (AND)
- **Any group** matching is enough (OR)

For example, "deliver urgent production alerts, or anything tagged `security`" is two groups:

| Group | Conditions |
|---|---|
| 1 | Priority **greater than** 4 AND Tags **has any of** `production` |
| 2 | Tags **has any of** `security` |

## Fields and Operators

### Priority

Priority uses the 1-5 scale (1 = Min, 2 = Low, 3 = Default, 4 = High, 5 = Urgent).

| Operator | Matches when |
|---|---|
| equals | Priority is exactly the value |
| does not equal | Priority is not the value |
| greater than | Priority is strictly greater than the value |
| less than | Priority is strictly less than the value |
| between | Priority is within the range, including both ends |

A notification without a priority only matches **does not equal**.

### Tags

| Operator | Matches when |
|---|---|
| has any of | At least one of the listed tags is present |
| has all of | Every listed tag is present |
| has none of | None of the listed tags are present |

Tag matching is case-insensitive.

### Title, Message & Payload

| Operator | Matches when |
|---|---|
| equals | The text is exactly the value |
| does not equal | The text is not the value |
| contains | The text includes the value |
| does not contain | The text does not include the value |
| starts with | The text begins with the value |
| matches regex | The value, as a regular expression, matches the text |

- All comparisons are **case-insensitive**, including regex.
- A missing title, or a payload path that doesn't exist, is treated as empty text. So **does not equal** and **does not contain** match, and the other operators don't.
- Regular expressions that could be very slow (catastrophic backtracking) are rejected at evaluation time and never match. Invalid regexes never match either.
- Values are limited to 1,000 characters.

### Payload paths

Payload conditions take a dot-separated **path** into the JSON request body. The path is resolved against the whole body, not only a `payload` key:

| Request body | Path | Value |
|---|---|---|
| `{"environment": "production"}` | `environment` | `production` |
| `{"payload": {"environment": "production"}}` | `payload.environment` | `production` |
| `{"alerts": [{"status": "firing"}]}` | `alerts.0.status` | `firing` |

Payload values are compared as text - there is no numeric comparison on payload fields. Non-text values are converted first: `42` becomes `"42"`, `true` becomes `"true"`, `null` becomes `"null"`, arrays become comma-separated values (`"a,b"`), and objects become `"[object Object]"` - so point the path at a single value. To route by severity, map it to the notification priority with a [priority template](/webhook-templates) and use a priority filter.

Plain-text notifications have no JSON payload, so payload conditions treat every path as missing (only **does not equal** and **does not contain** match).

## Testing a Filter

Each filtered channel has a test panel - click **Test filter** to open it. Enter a sample title, message, priority, tags, and JSON payload, then click **Run test** to see whether the notification would be delivered and which conditions matched. Invalid JSON in the payload field is treated as an empty object.

## Example Setup

**Scenario:** Critical notifications (priority 4+) go to Email and Telegram so you never miss them. Everything goes to Slack for team visibility.

| Channel | Filter | What it receives |
|---|---|---|
| Slack (#alerts) | No filter | All notifications |
| Email | Priority **greater than** 3 | Only priority 4 and 5 |
| Telegram | Priority **greater than** 3 | Only priority 4 and 5 |

A notification with `priority: 2` goes to Slack only. A notification with `priority: 5` goes to Slack, Email, and Telegram. A notification without a priority goes to Slack only.
