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

# Filter policies

> Select notifications with exact, prefix, suffix, exclusion, presence, and numeric matches.

A subscription filter selects which published messages produce a delivery for
that subscription. Put routing values in the publication's `attributes` map.
Putting an `event` field inside the JSON text of `message` does not make it a
filterable attribute.

## A complete example

This subscription policy accepts production orders worth at least 100 and less
than 1,000, unless the publication carries a `test` attribute:

```json theme={null}
{
  "event": ["order.created", "order.paid"],
  "environment": ["production"],
  "amount": [{ "numeric": [">=", 100, "<", 1000] }],
  "test": [{ "exists": false }]
}
```

These publication attributes match:

```json theme={null}
{
  "event": { "type": "String", "value": "order.paid" },
  "environment": { "type": "String", "value": "production" },
  "amount": { "type": "Number", "value": "149.50" }
}
```

Attribute values are strings even when their type is `Number`. Numeric matcher
operands are JSON numbers. Keep those two representations distinct.

| Change to the example attributes | Result | Reason |
| - | - | - |
| No change | Match | Every named attribute condition succeeds. |
| `event` becomes `order.refunded` | No match | Neither event alternative matches. |
| `amount` becomes `1000` | No match | The upper bound is exclusive. |
| Remove `amount` | No match | Numeric comparisons require a present attribute. |
| Add `test` with value `false` | No match | `exists: false` tests absence, not the value. |
| Add an unrelated `customer` attribute | Match | Extra attributes are ignored. |

## How conditions combine

Every attribute named by a policy must match: **AND across attribute names**.
Within one attribute's array, any alternative can match: **OR across
alternatives**. Inside a single `numeric` operator, every comparison must
succeed.

Names and string matches are case-sensitive. An empty policy `{}` matches
all messages, including messages with no attributes. Every matcher except
`exists: false` requires that the attribute be present. A missing attribute
therefore does not match an exclusion such as `anything-but`.

## Supported matchers

The examples below show the alternatives array for one attribute.

| Matcher | Example | Behavior |
| - | - | - |
| Exact string | `["critical", "error"]` | Value equals either string. |
| Prefix | `[{"prefix": "order."}]` | Value starts with `order.`. |
| Suffix | `[{"suffix": ".failed"}]` | Value ends with `.failed`. |
| Exclusion | `[{"anything-but": ["debug", "trace"]}]` | Present value equals neither item. A single string or number is also accepted. |
| Presence | `[{"exists": true}]` | Attribute exists, including an empty string value. |
| Absence | `[{"exists": false}]` | Attribute is absent. |
| Numeric range | `[{"numeric": [">=", 100, "<", 1000]}]` | Value parses as a number and satisfies both comparisons. |
| Numeric equality | `[{"numeric": ["=", 100]}]` | Compares numerically, so `100` and `100.0` both match. |

Supported numeric operators are `=`, `<`, `<=`, `>`, and `>=`. Write an
operator followed by its numeric operand; repeat pairs to form a range.
A value that cannot be parsed as a number does not match numeric conditions.
The current evaluator parses the value for numeric comparisons regardless of
its declared attribute type; use `Number` for numeric data for clarity.

Bare JSON numbers are also accepted as exact alternatives, but they compare
their string representation. `[100]` matches the attribute value `"100"`,
not `"100.0"`. Use `numeric` for mathematical equality.

A prefix or suffix matches literal text. There are no wildcards, regular
expressions, nested body matching, array-valued message attributes, or
case-insensitive operators. Do not assume every SNS filter operator is supported.

## Combining presence and exclusion

To accept a value other than `test`, including publications where the attribute
is absent, include both alternatives:

```json theme={null}
{
  "environment": [
    { "anything-but": "test" },
    { "exists": false }
  ]
}
```

To require a prefix and a suffix on the same value, two entries in the array
will not work: they mean OR. Publish a separate routing attribute with the
combined classification if your application needs that condition.

## Set or clear a policy

<Tabs>
  <Tab title="Console">
    Open a topic's **Subscriptions** tab and select **Subscribe**. Paste a
    JSON object into **Filter policy** on the creation page. Leave it empty to
    receive every message. The form checks JSON syntax; the API validates
    operators and limits.

    Open an existing subscription's **Filter policy** tab to inspect its policy;
    use the API to replace or clear it.
  </Tab>

  <Tab title="API">
    Set `filter_policy` when creating a subscription, or replace it with PATCH:

    ```http theme={null}
    PATCH /v1/topics/{topic_id}/subscriptions/{subscription_id}
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "filter_policy": {
        "severity": ["critical", "error"]
      }
    }
    ```

    PATCH replaces the complete policy; it does not merge attribute names.
    Send `{"filter_policy": {}}` to remove filtering. Omitting the field
    preserves the existing policy.
  </Tab>

  <Tab title="CLI">
    Save the JSON request from the API tab as `request-1.json`. Set `SUBSCRIPTION_ID`, `TOPIC_ID` to the resource UUIDs. Use your configured account and region.

    ```bash theme={null}
    basaltic notifications subscription update "$TOPIC_ID" "$SUBSCRIPTION_ID" --from-file request-1.json
    ```
  </Tab>

  <Tab title="Go">
    Save the JSON request from the API tab as `request-1.json`. Set `SUBSCRIPTION_ID`, `TOPIC_ID` to the resource UUIDs. Configure credentials, account, and region through the SDK environment variables.

    ```go theme={null}
    package main

    import (
    	"context"
    	"encoding/json"
    	"fmt"
    	"os"

    	basaltic "github.com/basaltic-sh/sdk-go"
    	"github.com/basaltic-sh/sdk-go/notifications"
    )

    func readJSON(path string, value any) error {
    	data, err := os.ReadFile(path)
    	if err != nil {
    		return err
    	}
    	return json.Unmarshal(data, value)
    }

    func run() error {
    	ctx := context.Background()
    	cfg, err := basaltic.NewConfig(ctx)
    	if err != nil {
    		return err
    	}
    	client := notifications.New(cfg)
    	var request1 notifications.UpdateSubscriptionRequest
    	if err := readJSON("request-1.json", &request1); err != nil {
    		return err
    	}
    	result1, err := client.UpdateSubscription(ctx, os.Getenv("TOPIC_ID"), os.Getenv("SUBSCRIPTION_ID"), &request1)
    	if err != nil {
    		return err
    	}
    	fmt.Printf("%+v\n", result1)
    	return nil
    }

    func main() {
    	if err := run(); err != nil {
    		fmt.Fprintln(os.Stderr, err)
    		os.Exit(1)
    	}
    }
    ```
  </Tab>
</Tabs>

## Limits and validation

| Item | Limit |
| - | - |
| Attribute names in one filter | 20 |
| Alternatives per attribute | 1–50 |
| Values in an `anything-but` list | 1–50 |
| Attributes on a published message | 20 |
| Published attribute name | 1–256 bytes |
| Published attribute value | At most 1,024 bytes |
| Published attribute types | `String` or `Number` |

Each operator object must have exactly one operator. Prefix and suffix strings
must be nonempty. Numeric lists must contain complete operator/number pairs.
Empty alternatives arrays and unknown operators are invalid. Malformed policies
return HTTP `400` with `NOTIFICATION_INVALID_FILTER_POLICY`.

A filtered-out subscription has no delivery record and is not a failed
attempt. If a publication reports fewer deliveries than expected, check the
subscription confirmation status, exact attribute names and values, and every
condition in the policy. A policy change affects fan-out decisions made after
the change; it does not retroactively create or remove existing delivery records.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.