SNS subscription filter policies
Write SNS subscription filter policies that match what you mean — attribute vs message-body scope, exact, anything-but (with prefix, suffix, wildcard), equals-ignore-case, prefix, suffix, wildcard, cidr, numeric ranges and exists — how String.Array attributes match, the limits (5 keys, 150 combinations, 256 KB, 200 policies per topic) and the 15-minute propagation delay.
markdownreferencev4WHD6-N7Q4stage: referencetrust: officialservice: snsinterface: anySNS filter policyFilterPolicyScope MessageAttributes MessageBodySNS filter on message bodyanything-butprefixsuffixwildcardequals-ignore-casecidrnumeric filter SNSexists filterString.ArrayNumber attributefilter policy 150 combinationsfilter policy 5 keysFilterPolicyLimitExceededExceptionfilter policy takes 15 minutesSNS filter not workingcase sensitive
**A filter policy sits on the subscription, not the topic, and every key in it must match. Without a policy, the subscriber receives everything. Policies filter message attributes unless the subscription's FilterPolicyScope is MessageBody, in which case the published message must be well-formed JSON. Changes can take up to 15 minutes** to take effect, so a test right after editing proves nothing.
Syntax
A policy maps each property name to a list of allowed values or operators. Values in one list are alternatives (OR); different keys must all match (AND). Properties the policy doesn't mention are ignored.
| Want | Policy fragment |
|---|---|
| Exact (any of) | "customer_interests": ["rugby", "tennis"] |
| Anything but | "customer_interests": [{"anything-but": ["rugby", "tennis"]}] |
| Anything but a prefix / suffix / wildcard | "event": [{"anything-but": {"prefix": "order-"}}], [{"anything-but": {"suffix": "ball"}}], [{"anything-but": {"wildcard": "*ball"}}] |
| Case-insensitive | "customer_interests": [{"equals-ignore-case": "tennis"}] |
| Prefix / suffix | [{"prefix": "bas"}] / [{"suffix": "ball"}] |
| Wildcard | "customer_interests": [{"wildcard": "*ball"}] |
| IP range | "source_ip": [{"cidr": "10.0.0.0/24"}] |
| Number equals | "price_usd": [{"numeric": ["=", 301.5]}] |
| Number range | "price_usd": [{"numeric": [">", 0, "<=", 150]}] |
| Number anything-but | "price": [{"anything-but": [100, 500]}] |
| Key present / absent | "store": [{"exists": true}] / "store": [{"exists": false}] |
Matching rules:
- Strings are case-sensitive. Quote strings; don't quote numbers.
- Numbers range from −10⁹ to 10⁹ with five decimal places. Number attributes have type
NumberorNumber.Array. String.Arrayattributes: each element is matched separately, so any matching element passes.anything-butpasses an array only if none of its elements is listed.exists: trueneeds a non-null, non-empty value.exists: falsematches only if the message has at least one attribute, so a message with no attributes at all doesn't match.existsworks on leaf keys only.anything-butcan be combined with"exists": false.- OR across different keys (
$or) has its own page, which wasn't collected. Check the SNS guide's "AND/OR logic" before using it.
Message-body scope
aws sns set-subscription-attributes --subscription-arn <arn> \
--attribute-name FilterPolicyScope --attribute-value MessageBody
aws sns set-subscription-attributes --subscription-arn <arn> \
--attribute-name FilterPolicy --attribute-value '{"detail":{"status":["PAID"]}}'
With body scope, nest the policy to mirror the JSON structure; only leaf keys count toward the key limit.
Limits
| Limit | Value |
|---|---|
| Keys per policy | 5 (leaf keys for nested policies) |
| Value combinations | 150: the product of the value counts per key, times the nesting level for nested keys |
| Policy size | 256 KB |
| Filter policies per topic / per account | Default quotas; the account one can be raised through Service Quotas. Read the live values rather than assuming them (FilterPolicyLimitExceededException when hit) |
When filtering seems wrong
| Symptom | Check |
|---|---|
| Subscriber gets nothing | Scope: the policy targets attributes, but the publisher put the field in the body, or the other way round. Case differences. exists: false on a message with no attributes. Edited less than 15 minutes ago |
| Subscriber gets everything | No FilterPolicy on this subscription. Policies are per subscription |
| Raw-delivery queue loses messages that pass the filter | More than 10 attributes with raw delivery: discarded (patterns/sns-to-sqs-fanout.md) |
EventBridge has a similar but richer pattern language (reference/eventbridge/event-patterns.md).