# Message filtering (/connectors/gcp-pub-sub/how-to/filtering)



A subscription may carry a `filter` so it only receives messages whose **attributes** match. The
connector implements a hand-written **CEL-subset** (a strict subset of Google's filter expression
language) — attributes-only, compiled once at create-time, and applied at publish fan-out.

## Where the filter runs [#where-the-filter-runs]

A filter is set on `CreateSubscription` and is **compiled once and immutable** thereafter
(`UpdateSubscription` cannot change `filter`). It is applied at **publish fan-out**: when a
`Publish` writes the topic log and fans copies out to each subscription, a message that does not
match a subscription's filter is **never enqueued** for that subscription (it is effectively
auto-acked for it). The topic log `gcp.{topic}` itself is unfiltered — the filter only governs which
subscription queues receive a copy. See
[Publishing](/connectors/gcp-pub-sub/how-to/publishing) and
[Channel mapping](/connectors/gcp-pub-sub/reference/channel-mapping).

## Supported syntax [#supported-syntax]

Filters operate on the message's &#x2A;*`attributes`** map only (not `data`, not the ordering key):

```text
attributes:KEY                       -- attribute KEY exists  (KEY may be quoted: attributes:"k")
attributes.KEY = "v"                 -- equality
attributes.KEY != "v"                -- inequality
hasPrefix(attributes.KEY, "p")       -- value has the prefix "p"
AND  OR  NOT  -                      -- boolean operators (NOT and unary - both negate)
( … )                                -- parentheses for grouping
```

Rules:

* **Attributes-only.** There is no `data`-based filtering and no numeric / comparison operators
  beyond `=` / `!=` / `hasPrefix`.
* **≤ 256 characters.** A filter expression longer than 256 chars is rejected.
* **Immutable.** Compiled at `CreateSubscription`; cannot be changed by `UpdateSubscription`.
* **Malformed → `INVALID_ARGUMENT`.** A filter that fails to parse is rejected at create-time, not
  silently ignored.

<Callout type="warn">
  **The filter is immutable and capped at 256 characters.** You cannot change `filter` after
  `CreateSubscription`; to alter the matching rule, create a new subscription. A filter longer than
  256 chars, or one that fails to parse, is rejected with `INVALID_ARGUMENT`. See
  [Limits & rules](/connectors/gcp-pub-sub/reference/limits-and-rules).
</Callout>

## Examples [#examples]

| Goal                                   | Filter                                                       |
| -------------------------------------- | ------------------------------------------------------------ |
| Only messages tagged `region=eu`       | `attributes.region = "eu"`                                   |
| Everything except `region=eu`          | `attributes.region != "eu"`                                  |
| Has a `priority` attribute (any value) | `attributes:priority`                                        |
| `type=order` **and** high priority     | `attributes.type = "order" AND attributes.priority = "high"` |
| EU **or** US region                    | `attributes.region = "eu" OR attributes.region = "us"`       |
| Order events but not test traffic      | `attributes.type = "order" AND NOT attributes:test`          |
| Event names starting with `user.`      | `hasPrefix(attributes.event, "user.")`                       |
| A quoted key with special chars        | `attributes:"x-tenant"`                                      |

A filter is just a field on the subscription config:

```python
from google.cloud import pubsub_v1

subscriber = pubsub_v1.SubscriberClient()    # honours PUBSUB_EMULATOR_HOST
subscriber.create_subscription(request={
    "name": subscriber.subscription_path("my-project", "sub-eu"),
    "topic": "projects/my-project/topics/orders",
    "filter": 'attributes.region = "eu"',    # compiled once; immutable
})
```

## How attributes map [#how-attributes-map]

Message `attributes` round-trip as KubeMQ message tags. The connector also carries three reserved
tags (`_pubsub_message_id`, `_pubsub_publish_time`, `_pubsub_ordering_key`); filters operate on the
**user attributes**, not the reserved tags. See
[Publishing](/connectors/gcp-pub-sub/how-to/publishing).

## The fan-out pattern [#the-fan-out-pattern]

Filtering is the basis of the fan-out pattern: attach **N subscriptions** to one topic, each with
its own filter, and a single `Publish` is fanned out only to the subscriptions whose filter matches.
See [Fan-out](/connectors/gcp-pub-sub/how-to/fan-out).

## Error quick reference [#error-quick-reference]

| Trigger                                             | Result                                     |
| --------------------------------------------------- | ------------------------------------------ |
| Malformed / unparseable filter                      | `INVALID_ARGUMENT` at `CreateSubscription` |
| Filter > 256 characters                             | `INVALID_ARGUMENT`                         |
| Attempt to change `filter` via `UpdateSubscription` | rejected (immutable)                       |
| Non-attribute (e.g. `data`-based) expression        | `INVALID_ARGUMENT`                         |

## Related [#related]

<Cards>
  <Card title="Fan-out" href="/connectors/gcp-pub-sub/how-to/fan-out" description="One publish, many subscriptions — each with its own filter and ack state, fanned out over KubeMQ." />

  <Card title="Subscribing" href="/connectors/gcp-pub-sub/how-to/subscribing" description="CreateSubscription, the immutable filter, Pull vs StreamingPull, and the ack-deadline lease." />

  <Card title="Limits & rules" href="/connectors/gcp-pub-sub/reference/limits-and-rules" description="The 256-char filter limit alongside the connector's other numeric limits and validation rules." />
</Cards>
