Back to all articles

EventBridge Pattern Matching: A Field Guide

November 17, 20259 min readProduct Insights

Introduction

Writing effective patterns requires understanding how EventBridge interprets the JSON structure you provide. Simple patterns are straightforward, but as patterns grow complex-especially with nested $or operators-the matching logic becomes harder to reason about.

AWS's official documentation covers the mechanics, but it's intentionally terse. When you're building event-driven architectures and need to detect specific conditions across CloudTrail events, security alerts, or application events, you need more than syntax reference-you need mental models for how patterns actually work.

This guide walks through pattern construction step by step, from basic field matching to complex multi-level logic. Throughout, we'll use boolean algebra notation alongside the JSON patterns. This isn't something you need to write yourself-it's a tool to help visualize and reason about the logic of your rule. Whether you're building detection rules, automating responses, or orchestrating workflows, understanding pattern expansion will help you write rules that match exactly what you intend.

Foundation: Fields Are AND, Arrays Are OR

Two core rules govern EventBridge pattern evaluation:

  1. Multiple fields at the same level = AND (all must match)
  2. Multiple values in an array = OR (any can match)

Pattern:

1{
2 "source": ["aws.iam"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "eventName": ["CreateAccessKey", "DeleteAccessKey", "UpdateAccessKey"]
6 }
7}
8

Boolean Algebra:

1(
2 source = "aws.iam"
3 AND detail-type = "AWS API Call via CloudTrail"
4 AND (
5 detail.eventName = "CreateAccessKey"
6 OR detail.eventName = "DeleteAccessKey"
7 OR detail.eventName = "UpdateAccessKey"
8 )
9)
10

The source and detail-type fields are ANDed together-both must match. The eventName array creates OR logic: the event matches if eventName equals any of the three values.

Matching Event:

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.iam",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "CreateAccessKey",
13 "eventSource": "iam.amazonaws.com",
14 "userIdentity": {
15 "type": "IAMUser",
16 "principalId": "AIDAI23HXD4EXAMPLE",
17 "arn": "arn:aws:iam::123456789012:user/alice",
18 "accountId": "123456789012",
19 "userName": "alice"
20 },
21 "requestParameters": {
22 "userName": "bob"
23 },
24 "responseElements": {
25 "accessKey": {
26 "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
27 "status": "Active",
28 "userName": "bob",
29 "createDate": "2025-01-15T11:45:00Z"
30 }
31 }
32 }
33}
34

The $or Operator: OR Between Different Fields

Field-level arrays create OR within a single field. When you need OR logic between different fields, use the $or operator.

Pattern:

1{
2 "source": ["aws.iam"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "eventName": ["AssumeRole"],
6 "$or": [
7 {
8 "errorCode": ["AccessDenied"]
9 },
10 {
11 "userIdentity": {
12 "type": ["Root"]
13 }
14 }
15 ]
16 }
17}
18

Boolean Algebra:

1(
2 source = "aws.iam"
3 AND detail-type = "AWS API Call via CloudTrail"
4 AND (
5 detail.eventName = "AssumeRole"
6 AND (
7 detail.errorCode = "AccessDenied"
8 OR detail.userIdentity.type = "Root"
9 )
10 )
11)
12

This matches AssumeRole calls that either failed with AccessDenied OR were made by the Root user.

Matching Event (Root user):

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.iam",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "AssumeRole",
13 "eventSource": "sts.amazonaws.com",
14 "userIdentity": {
15 "type": "Root",
16 "principalId": "123456789012",
17 "arn": "arn:aws:iam::123456789012:root",
18 "accountId": "123456789012"
19 },
20 "requestParameters": {
21 "roleArn": "arn:aws:iam::123456789012:role/ProductionRole",
22 "roleSessionName": "RootSession"
23 },
24 "responseElements": {
25 "credentials": {
26 "accessKeyId": "ASIAIOSFODNN7EXAMPLE",
27 "expiration": "2025-01-15T13:00:00Z"
28 }
29 }
30 }
31}
32

Matching Event (AccessDenied):

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.iam",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "AssumeRole",
13 "eventSource": "sts.amazonaws.com",
14 "errorCode": "AccessDenied",
15 "errorMessage": "User: arn:aws:iam::123456789012:user/alice is not authorized to perform: sts:AssumeRole",
16 "userIdentity": {
17 "type": "IAMUser",
18 "principalId": "AIDAI23HXD4EXAMPLE",
19 "arn": "arn:aws:iam::123456789012:user/alice",
20 "accountId": "123456789012",
21 "userName": "alice"
22 },
23 "requestParameters": null,
24 "responseElements": null
25 }
26}
27

Pattern Operators

EventBridge supports matching operators beyond exact equality.

prefix / suffix

Match strings that start or end with a specific value.

Pattern:

1{
2 "detail": {
3 "eventName": [{ "prefix": "Delete" }]
4 }
5}
6

Matches: DeleteBucket, DeleteObject, DeleteUser

wildcard

Glob pattern matching using * as wildcard.

Pattern:

1{
2 "detail": {
3 "eventSource": [{ "wildcard": "*.amazonaws.com" }]
4 }
5}
6

Matches: s3.amazonaws.com, ec2.amazonaws.com, iam.amazonaws.com

numeric

Number comparisons with operators: >, >=, <, <=, =.

Pattern:

1{
2 "detail": {
3 "responseElements": {
4 "httpStatusCode": [{ "numeric": [">=", 400, "<", 500] }]
5 }
6 }
7}
8

Matches: 400, 404, 403, 499 (client errors)

exists

Check for field presence or absence.

Pattern:

1{
2 "detail": {
3 "errorCode": [{ "exists": true }],
4 "responseElements": [{ "exists": false }]
5 }
6}
7

Matches events that have an error code but no response.

cidr

IP address matching within CIDR range.

Pattern:

1{
2 "detail": {
3 "sourceIPAddress": [{ "cidr": "10.0.0.0/8" }]
4 }
5}
6

Matches: 10.0.0.1, 10.255.255.255

equals-ignore-case

Case-insensitive string matching.

Pattern:

1{
2 "detail": {
3 "errorCode": [{ "equals-ignore-case": "accessdenied" }]
4 }
5}
6

Matches: AccessDenied, ACCESSDENIED, accessdenied

anything-but

Negation operator. Matches when value does NOT equal any of the specified values.

Pattern:

1{
2 "detail": {
3 "eventName": [{ "anything-but": ["GetObject", "HeadObject"] }]
4 }
5}
6

Matches any eventName EXCEPT GetObject or HeadObject.

Important: anything-but with multiple values uses AND logic. The value must NOT match "GetObject" AND must NOT match "HeadObject".

anything-but with Nested Operators

anything-but can wrap other operators to negate them.

Pattern:

1{
2 "detail.userIdentity.userName": [
3 {
4 "anything-but": {
5 "prefix": ["system-", "service-"]
6 }
7 }
8 ]
9}
10

When anything-but contains multiple values in an operator like {"prefix": ["test-", "dev-"]}, it creates AND logic. The value must NOT start with "test-" AND must NOT start with "dev-".

1(
2 detail.userIdentity.userName NOT_STARTS_WITH "system-"
3 AND detail.userIdentity.userName NOT_STARTS_WITH "service-"
4)
5

Pattern Expansion: How $or Works

EventBridge doesn't evaluate $or at runtime. During rule creation, it expands patterns containing $or into multiple sub-patterns-one for each possible path through all OR branches.

Pattern:

1{
2 "source": ["aws.iam"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "eventName": ["AssumeRole"],
6 "$or": [
7 { "errorCode": ["AccessDenied"] },
8 { "userIdentity": { "type": ["Root"] } }
9 ]
10 }
11}
12

EventBridge expands this to 2 sub-patterns:

Sub-pattern 1:

1source = "aws.iam"
2AND detail-type = "AWS API Call via CloudTrail"
3AND eventName = "AssumeRole"
4AND errorCode = "AccessDenied"
5

Sub-pattern 2:

1source = "aws.iam"
2AND detail-type = "AWS API Call via CloudTrail"
3AND eventName = "AssumeRole"
4AND userIdentity.type = "Root"
5

An event matches if it satisfies ANY complete sub-pattern. All fields outside $or are distributed to every sub-pattern.


Multiple $or: Combinatorial Expansion

When you use multiple $or operators in a pattern, they multiply to create more sub-patterns.

Pattern:

1{
2 "source": ["aws.s3"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "$or": [
6 { "eventName": ["PutBucketPolicy"] },
7 { "eventName": ["PutBucketAcl"] }
8 ],
9 "requestParameters": {
10 "$or": [{ "acl": ["public-read"] }, { "acl": ["public-read-write"] }]
11 }
12 }
13}
14

Expansion: 2 branches × 2 branches = 4 sub-patterns

  1. eventName = "PutBucketPolicy" AND acl = "public-read"
  2. eventName = "PutBucketPolicy" AND acl = "public-read-write"
  3. eventName = "PutBucketAcl" AND acl = "public-read"
  4. eventName = "PutBucketAcl" AND acl = "public-read-write"

Three $or operators with 2 branches each: 2 × 2 × 2 = 8 sub-patterns.

Important: Field-level arrays don't multiply sub-patterns-they stay intact within each sub-pattern. An array like ["CreateAccessKey", "DeleteAccessKey"] creates OR logic within the sub-pattern, not additional sub-patterns.

Matching Event:

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.s3",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "PutBucketAcl",
13 "eventSource": "s3.amazonaws.com",
14 "requestParameters": {
15 "bucketName": "my-public-bucket",
16 "acl": "public-read"
17 },
18 "responseElements": null,
19 "userIdentity": {
20 "type": "IAMUser",
21 "principalId": "AIDAI23HXD4EXAMPLE",
22 "arn": "arn:aws:iam::123456789012:user/alice",
23 "accountId": "123456789012",
24 "userName": "alice"
25 }
26 }
27}
28

Nested $or: Hierarchical Logic

You can nest $or within $or branches to create complex logic trees.

Pattern:

1{
2 "source": ["aws.iam"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "$or": [
6 {
7 "eventName": ["CreateAccessKey"],
8 "$or": [
9 { "userIdentity": { "type": ["Root"] } },
10 { "userIdentity": { "type": ["AssumedRole"] } }
11 ]
12 },
13 {
14 "eventName": ["CreateUser"],
15 "requestParameters": {
16 "$or": [
17 { "userName": [{ "prefix": "admin-" }] },
18 { "userName": [{ "prefix": "root-" }] }
19 ]
20 }
21 }
22 ]
23 }
24}
25

Expands to 4 sub-patterns:

  1. CreateAccessKey AND type = "Root"
  2. CreateAccessKey AND type = "AssumedRole"
  3. CreateUser AND userName starts with "admin-"
  4. CreateUser AND userName starts with "root-"

The outer $or creates two main paths. Each path contains its own $or, which further subdivides that path.

Matching Event:

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.iam",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "CreateAccessKey",
13 "eventSource": "iam.amazonaws.com",
14 "userIdentity": {
15 "type": "Root",
16 "principalId": "123456789012",
17 "arn": "arn:aws:iam::123456789012:root",
18 "accountId": "123456789012"
19 },
20 "requestParameters": {
21 "userName": "test-user"
22 },
23 "responseElements": {
24 "accessKey": {
25 "accessKeyId": "AKIAIOSFODNN7EXAMPLE",
26 "status": "Active",
27 "userName": "test-user",
28 "createDate": "2025-01-15T16:00:00Z"
29 }
30 }
31 }
32}
33

Root-Level $or: Independent Patterns

The $or operator can appear at the root level to match completely different event types in a single rule.

Pattern:

1{
2 "detail-type": ["AWS API Call via CloudTrail"],
3 "$or": [
4 {
5 "source": ["aws.s3"],
6 "detail": {
7 "eventName": ["DeleteBucket"]
8 }
9 },
10 {
11 "source": ["aws.iam"],
12 "detail": {
13 "eventName": ["CreateAccessKey"]
14 }
15 }
16 ]
17}
18
1(
2 detail-type = "AWS API Call via CloudTrail"
3 AND (
4 (
5 source = "aws.s3"
6 AND detail.eventName = "DeleteBucket"
7 )
8 OR (
9 source = "aws.iam"
10 AND detail.eventName = "CreateAccessKey"
11 )
12 )
13)
14

This single rule matches operations across two different AWS services. Each branch is completely independent.

Matching Event (S3):

1{
2 "version": "0",
3 "id": "c7118dc7-e242-4e48-ab7c-4c65e5a6b9d7",
4 "detail-type": "AWS API Call via CloudTrail",
5 "source": "aws.s3",
6 "account": "123456789012",
7 "time": "2025-01-15T10:30:00Z",
8 "region": "us-east-1",
9 "resources": [],
10 "detail": {
11 "eventVersion": "1.08",
12 "eventName": "DeleteBucket",
13 "eventSource": "s3.amazonaws.com",
14 "requestParameters": {
15 "bucketName": "old-test-bucket"
16 },
17 "responseElements": null,
18 "userIdentity": {
19 "type": "IAMUser",
20 "principalId": "AIDAI23HXD4EXAMPLE",
21 "arn": "arn:aws:iam::123456789012:user/alice",
22 "accountId": "123456789012",
23 "userName": "alice"
24 }
25 }
26}
27

Three-Level Nesting Example

Pattern:

1{
2 "source": ["aws.s3"],
3 "detail-type": ["AWS API Call via CloudTrail"],
4 "detail": {
5 "readOnly": [false],
6 "$or": [
7 {
8 "eventSource": ["s3.amazonaws.com"],
9 "$or": [
10 {
11 "eventName": ["DeleteBucket", "DeleteBucketPolicy"],
12 "errorCode": [{ "exists": false }]
13 },
14 {
15 "eventName": ["PutBucketAcl"],
16 "requestParameters": {
17 "$or": [
18 { "acl": ["public-read"] },
19 { "acl": ["public-read-write"] },
20 { "grantFullControl": [{ "wildcard": "*" }] }
21 ]
22 }
23 }
24 ]
25 },
26 {
27 "eventSource": ["iam.amazonaws.com"],
28 "eventName": [{ "prefix": "Delete" }],
29 "userIdentity": {
30 "type": ["IAMUser"],
31 "$or": [
32 { "userName": [{ "prefix": "admin-" }] },
33 { "userName": [{ "prefix": "service-" }] }
34 ]
35 }
36 }
37 ]
38 }
39}
40

Expansion:

S3 branch:

  • Sub-pattern 1: Delete bucket operations (successful)
  • Sub-pattern 2a: PutBucketAcl with public-read
  • Sub-pattern 2b: PutBucketAcl with public-read-write
  • Sub-pattern 2c: PutBucketAcl with grantFullControl wildcard

IAM branch:

  • Sub-pattern 3a: Delete operations by IAMUser with username starting "admin-"
  • Sub-pattern 3b: Delete operations by IAMUser with username starting "service-"

Total: 6 sub-patterns

Each sub-pattern includes readOnly = false from the root level.

To verify our intuition

1(
2 source = "aws.s3"
3 AND detail-type = "AWS API Call via CloudTrail"
4 AND (
5 detail.readOnly = false
6 AND (
7 (
8 detail.eventSource = "s3.amazonaws.com"
9 AND (
10 (
11 (
12 detail.eventName = "DeleteBucket"
13 OR detail.eventName = "DeleteBucketPolicy"
14 )
15 AND detail.errorCode NOT_EXISTS
16 )
17 OR (
18 detail.eventName = "PutBucketAcl"
19 AND (
20 (
21 detail.requestParameters.acl = "public-read"
22 OR detail.requestParameters.acl = "public-read-write"
23 OR detail.requestParameters.grantFullControl MATCHES_WILDCARD "*"
24 )
25 )
26 )
27 )
28 )
29 OR (
30 detail.eventSource = "iam.amazonaws.com"
31 AND detail.eventName STARTS_WITH "Delete"
32 AND (
33 detail.userIdentity.type = "IAMUser"
34 AND (
35 detail.userIdentity.userName STARTS_WITH "admin-"
36 OR detail.userIdentity.userName STARTS_WITH "service-"
37 )
38 )
39 )
40 )
41 )
42)
43

Common Pitfalls

Match-All Events

An empty rule, {} while makes sense to catch everything - EventBridge does not allow empty objects - thus, a simple rule that validates the source exists can be used to match all events.

1{
2 "source": [{ "exists": true }]
3}
4

The Root-Level Field Trap

Mixing root-level fields with root-level $or creates logical contradictions that make branches unreachable.

Pattern (INCORRECT):

1{
2 "source": ["aws.s3"],
3 "$or": [
4 {
5 "source": ["aws.s3"],
6 "detail": {
7 "eventName": ["DeleteBucket"]
8 }
9 },
10 {
11 "source": ["aws.cloudtrail"],
12 "detail": {
13 "eventName": ["DeleteTrail"]
14 }
15 }
16 ]
17}
18

The root-level source = "aws.s3" gets ANDed with BOTH branches:

  • Branch 1: source = "aws.s3" AND source = "aws.s3" - Redundant but works
  • Branch 2: source = "aws.s3" AND source = "aws.cloudtrail" - Impossible
1(
2 source = "aws.s3"
3 AND (
4 (
5 detail.eventName = "DeleteBucket"
6 AND source = "aws.s3"
7 )
8 OR (
9 source = "aws.cloudtrail"
10 AND detail.eventName = "DeleteTrail"
11 )
12 )
13)
14

The second branch can never match because a source field cannot equal two different values simultaneously.

The Fix:

1{
2 "$or": [
3 {
4 "source": ["aws.s3"],
5 "detail": { "eventName": ["DeleteBucket"] }
6 },
7 {
8 "source": ["aws.cloudtrail"],
9 "detail": { "eventName": ["DeleteTrail"] }
10 }
11 ]
12}
13

Sub-Pattern Explosion

Each $or operator multiplies the number of sub-patterns:

  • 2 $or with 2 branches each = 4 sub-patterns
  • 3 $or with 2 branches each = 8 sub-patterns
  • 3 $or with 3 branches each = 27 sub-patterns

If your pattern creates too many sub-patterns, consider:

  • Reducing nesting depth
  • Splitting into multiple rules
  • Using field-level arrays instead of $or where possible

As a fun example, here's a less than optimal example to match all - that should be easy to follow/understand why exactly..

1{
2 "$or": [
3 {
4 "source": [{ "prefix": "" }],
5 "$or": [
6 {
7 "source": [{ "wildcard": "*" }],
8 "$or": [
9 { "source": [{ "exists": true }] },
10 { "source": [{ "suffix": "" }] }
11 ]
12 },
13 {
14 "source": [{ "anything-but": "absolutely-never-this-source" }],
15 "$or": [
16 {
17 "source": [{ "equals-ignore-case": "aws.s3" }, "aws.s3", "AWS.S3"]
18 },
19 { "source": [{ "wildcard": "aws.*" }] }
20 ]
21 }
22 ]
23 },
24 {
25 "source": [{ "wildcard": "*.amazonaws.com" }],
26 "$or": [
27 {
28 "source": [{ "prefix": "aws" }],
29 "$or": [
30 { "source": [{ "suffix": "com" }] },
31 { "source": [{ "exists": true }] }
32 ]
33 },
34 {
35 "source": [{ "anything-but": { "prefix": "never-matches-" } }],
36 "$or": [
37 { "source": [{ "wildcard": "*.*" }] },
38 { "source": [{ "prefix": "" }] }
39 ]
40 }
41 ]
42 }
43 ]
44}
45
1(
2 (
3 (
4 source STARTS_WITH ""
5 AND (
6 (
7 source MATCHES_WILDCARD "*"
8 AND (
9 source EXISTS
10 OR source ENDS_WITH ""
11 )
12 )
13 OR (
14 source != "absolutely-never-this-source"
15 AND (
16 (
17 source EQUALS_IGNORECASE "aws.s3"
18 OR source = "aws.s3"
19 OR source = "AWS.S3"
20 )
21 OR source MATCHES_WILDCARD "aws.*"
22 )
23 )
24 )
25 )
26 OR (
27 source MATCHES_WILDCARD "*.amazonaws.com"
28 AND (
29 (
30 source STARTS_WITH "aws"
31 AND (
32 source ENDS_WITH "com"
33 OR source EXISTS
34 )
35 )
36 OR (
37 source NOT_STARTS_WITH "never-matches-"
38 AND (
39 source MATCHES_WILDCARD "*.*"
40 OR source STARTS_WITH ""
41 )
42 )
43 )
44 )
45 )
46)
47

Pattern Boundaries

Field-level arrays already provide OR logic:

1{
2 "detail": {
3 "eventName": [{ "prefix": "Create" }, { "prefix": "Delete" }]
4 }
5}
6

This means: eventName starts with "Create" OR eventName starts with "Delete"

1(
2 detail.eventName STARTS_WITH "Create"
3 OR detail.eventName STARTS_WITH "Delete"
4)
5

Empty arrays/objects not allowed:

You need at least two objects within an $or relationship.

1{
2 "detail": {
3 "$or": [] // INVALID
4 }
5}
6

Pattern Construction Strategy

When writing patterns:

1. Identify AND relationships:

Place these as sibling fields.

1{
2 "field1": ["value1"],
3 "field2": ["value2"]
4}
5

2. Identify OR relationships within a field:

Use field-level arrays.

1{
2 "eventName": ["Create", "Delete", "Update"]
3}
4

3. Identify OR relationships between fields:

Use the $or operator.

1{
2 "$or": [{ "field1": ["value1"] }, { "field2": ["value2"] }]
3}
4

4. Count sub-pattern explosion:

Each $or multiplies sub-patterns. Verify this matches your intent.

5. Reason about the logic:

Does the pattern capture your intent? Are there contradictions or unreachable branches?

6. Verify field paths:

Ensure you're using the correct field nesting. detail.eventName vs eventName without any nesting matters!


Debugging Pattern Matching Issues

When patterns don't match expected events:

  1. Trace the pattern's logic
  2. Check each sub-pattern's constraints
  3. Verify constraints aren't contradictory
  4. Check field paths
  5. Confirm operator semantics

Example of a problematic pattern:

1{
2 "source": ["aws.s3"],
3 "$or": [
4 {
5 "source": ["aws.s3"],
6 "detail": {
7 "eventName": ["DeleteBucket"]
8 }
9 },
10 {
11 "source": ["aws.cloudtrail"],
12 "detail": {
13 "eventName": ["DeleteTrail"]
14 }
15 }
16 ]
17}
18

The second branch can never match because of the root-level source = "aws.s3" constraint creating a contradiction with source = "aws.cloudtrail".


Conclusion

EventBridge patterns combine these primitives:

  • Fields create AND relationships
  • Arrays create OR relationships within fields
  • $or creates OR relationships between fields
  • Nesting creates hierarchical evaluation paths
  • Operators modify field matching semantics

Understanding pattern expansion-how $or unfolds into sub-patterns-is key to writing patterns that work as intended.

Good patterns:

  1. Match what you intend to detect
  2. Don't have logical contradictions
  3. Are readable when you revisit them later

Reasoning about the boolean logic can help validate patterns and debug when things don't match as expected.

Want more insights like this?