Fan-out delivers every message on a topic to every subscriber. Often that is too much - a billing queue only cares about payment events, a shipping queue only cares about fulfillment events, but both are subscribed to the same order-events topic.
Subscription filter policies solve this without splitting the topic. Each subscription gets its own FilterPolicy, and SNS only delivers messages that match it.
A filter policy is JSON where each key is an attribute name and the value is an array of conditions, all of which are ORed together for that key, with multiple keys ANDed together. Conditions can be exact strings, but also richer matchers:
Other supported conditions include prefix matching ({"prefix": "us-"}), exists/does-not-exist checks, and anything-but exclusions ({"anything-but": ["test"]}). These let a subscriber express fairly specific routing rules without any custom logic in the publisher.
By default, FilterPolicyScope is MessageAttributes - the filter matches against the MessageAttributes sent with Publish, not the message body itself. Setting it to MessageBody instead lets the filter match directly against JSON fields inside the published message body, which is convenient when the payload is already structured JSON and you do not want to duplicate fields into message attributes just for filtering.
Set the scope before or alongside the FilterPolicy itself - both are subscription attributes, and both must agree on where to look for the fields the policy references.
The publisher never needs to know who is subscribed or what they each want. It just publishes with enough attributes (or a rich enough body) for subscribers to filter on. This is the main structural benefit over one-topic-per-event-type: adding a new consumer interest is a subscription-side change only.
Publisher omits the attribute the filter checks - if eventType is never set on Publish, no subscriber whose filter requires it will ever match. Fix: always include the attributes your subscribers filter on, and consider a policy allowing "attribute does not exist" if that is intentional.
Mismatched FilterPolicyScope - a filter written for MessageBody fields does nothing if the subscription's scope is still MessageAttributes. Fix: set FilterPolicyScope to match how the filter policy is written.
Assuming a non-matching message errors - it does not. Publish succeeds regardless; the message is silently not delivered to non-matching subscribers. Fix: verify filters with test publishes and check delivery, not error absence.
Overly broad filters that match everything - defeats the purpose and re-creates the unfiltered fan-out load. Fix: review filter conditions to ensure they actually narrow the traffic each subscriber wants.
JSON body filters on non-JSON payloads - MessageBody scope requires the body to be valid JSON; a plain string body cannot be filtered this way. Fix: use MessageAttributes scope for non-JSON payloads.
Forgetting filters are per-subscription, not per-topic - assuming one filter policy applies topic-wide. Fix: set FilterPolicy on each subscription individually; there is no topic-level default.
What does SNS match a FilterPolicy against by default?
The MessageAttributes sent with Publish. Set FilterPolicyScope to MessageBody to match against JSON fields in the message body instead.
What happens if a message doesn't match a subscriber's filter?
That subscriber simply does not receive it. The Publish call still succeeds and other subscribers whose filters do match still receive their copy.
Can a filter policy match more than exact string equality?
Yes. It supports arrays of values (OR), numeric range conditions, prefix matching, exists/does-not-exist, and anything-but exclusions, combined across multiple attribute keys with AND.
Do I need a different topic for each event type to filter?
No. That is exactly what filter policies avoid - one topic, multiple subscribers, each with its own filter policy scoped to the event types it cares about.
Can I filter on the message body instead of attributes?
Yes, set FilterPolicyScope to MessageBody on the subscription. The body must be valid JSON for the filter to evaluate its fields.
Is filtering applied per-topic or per-subscription?
Per-subscription. Each subscription has its own independent FilterPolicy, so different subscribers to the same topic can have entirely different rules.