Skip to content

Transform Filter Deep Dive

The @orgloop/transform-filter is OrgLoop’s built-in event filter. It handles the vast majority of filtering needs without writing code — from simple field matching to full jq expressions. Understanding its capabilities means you rarely need to build a custom filter.

The filter operates in one of two modes:

  1. Match/Exclude mode — Declarative dot-path field matching. Fast, in-process, no dependencies.
  2. jq mode — Full jq expression evaluation. Subprocess-based, requires jq installed. The escape hatch for anything match/exclude can’t do.

If both are specified, jq takes precedence and match/exclude are ignored.

Three operations, evaluated in this order:

OperationLogicEffect
excludeOR — any criterion matching drops the eventRuns first. If any exclusion matches, event is dropped immediately.
matchAND — all criteria must matchRuns second. All criteria must pass for the event to continue.
match_anyOR — any criterion matching keeps the eventRuns third. At least one criterion must pass.

All three can be combined in a single filter. The evaluation order is always exclude → match → match_any, regardless of YAML field order.

All criteria must match. Use this for “keep only events that look like X.”

transforms:
- name: human-pr-reviews
type: package
package: "@orgloop/transform-filter"
config:
match:
type: "resource.changed"
provenance.platform_event: "pull_request.review_submitted"
provenance.author_type: "team_member"

This passes events that are resource.changed AND are PR review submissions AND are from a team member. All three must be true.

At least one criterion must match. Use this for “keep events matching any of these.”

config:
match_any:
provenance.pr_author: "alice"
provenance.reviewer: "bob"

This passes events where the PR author is alice OR the reviewer is bob.

Any criterion matching drops the event. Use this for “drop events matching any of these.”

config:
exclude:
provenance.author:
- "dependabot[bot]"
- "renovate[bot]"
provenance.author_type: "bot"

This drops events where the author is dependabot OR renovate OR the author type is bot. Any single match drops the event.

config:
exclude:
provenance.author_type: "bot"
match:
type: "resource.changed"
match_any:
provenance.pr_author: "alice,bob"
provenance.reviewer: "charlie"

Evaluation: First, if the author type is bot → dropped. Then, event must be resource.changed. Then, the PR author must be alice or bob, OR the reviewer must be charlie.

All field references use dot-separated paths to traverse nested objects.

PathResolves to
typeevent.type
sourceevent.source
provenance.authorevent.provenance.author
provenance.author_typeevent.provenance.author_type
payload.pr_numberevent.payload.pr_number
payload.labelsevent.payload.labels (array)

If any segment in the path is missing or not an object, the value is treated as undefined and won’t match any pattern (safe, no crash).

The filter supports several pattern types for matching field values.

Strings, numbers, and booleans use strict equality.

match:
type: "resource.changed" # String
payload.count: 42 # Number
payload.draft: false # Boolean

An array in a pattern means “match if the field equals any element.”

exclude:
provenance.author:
- "dependabot[bot]"
- "renovate[bot]"
- "github-actions[bot]"

This drops the event if provenance.author equals any of those three values.

String values containing commas are automatically expanded to arrays during initialization. This is especially useful with environment variable substitution.

match_any:
provenance.pr_author: "alice,bob,charlie"

Is equivalent to:

match_any:
provenance.pr_author:
- "alice"
- "bob"
- "charlie"

Whitespace around commas is trimmed. Combined with env vars:

match_any:
provenance.pr_author: "${TEAM_MEMBERS}" # TEAM_MEMBERS=alice,bob,charlie
exclude:
provenance.author: "${EXCLUDED_BOTS}" # EXCLUDED_BOTS=dependabot[bot],renovate[bot]

Wrap a pattern in forward slashes for regular expression matching.

match:
payload.cwd: '/^\/Users\/alice\/work\//' # Path starts with /Users/alice/work/
payload.title: '/fix|bug/i' # Case-insensitive match for "fix" or "bug"
payload.branch: '/^feature\//' # Branch starts with feature/

Syntax: /pattern/flags where flags are optional. Supported flags include i (case-insensitive).

If the regex is invalid, the filter falls back to exact string matching (safe failure).

match:
provenance.reviewer: null # Matches when reviewer is null or undefined

When match/exclude can’t express what you need, jq mode gives you the full power of jq expressions. This is the answer to “how do I filter on X?” for any X.

jq mode requires jq installed on the system (brew install jq, apt install jq, etc.).

transforms:
- name: complex-filter
type: package
package: "@orgloop/transform-filter"
config:
jq: '<jq expression>'

The filter pipes the entire event JSON to jq -e '<expression>' as a subprocess (5-second timeout).

jq outputResult
Exit 0, valid JSON object with id fieldEvent replaced with jq output
Exit 0, truthy non-JSON outputEvent passes unchanged
Exit 0, null or falseEvent dropped
Non-zero exitEvent dropped
Timeout (5s)Event dropped

The expression evaluates to true/false. True keeps the event unchanged, false drops it.

# Filter by array contents
config:
jq: '.payload.labels | any(.name == "needs-review")'
# Filter by author with OR logic
config:
jq: '.provenance.pr_author == "alice" or .provenance.pr_author == "bob"'
# Complex: array contains + author check
config:
jq: >
(.payload.labels | any(.name == "urgent")) and
.provenance.author_type == "team_member"
# Negative: exclude events with "wip" label
config:
jq: '(.payload.labels | any(.name == "wip")) | not'

jq can also transform the event. If the output is a valid JSON object with an id field, it replaces the original event.

# Add a computed field and pass through
config:
jq: '. + {metadata: {label_count: (.payload.labels | length)}}'

Only PR reviews from non-bot authors on non-draft PRs:

config:
jq: >
.provenance.author_type != "bot" and
(.payload.draft | not) and
.provenance.platform_event == "pull_request.review_submitted"

Only events where the PR has specific labels:

config:
jq: '.payload.labels | any(.name == "niko-authored")'

Only events from specific authors:

config:
jq: '.provenance.pr_author == "doink-kindo[bot]" or .provenance.pr_author == "c-h-"'

Filter CI failures (exclude successes):

config:
jq: '.payload.conclusion == "failure" or .payload.conclusion == "timed_out"'

Array intersection (PR has any of these labels):

config:
jq: '[.payload.labels[].name] | any(. == "bug" or . == "critical" or . == "security")'
NeedModeExample
Match a single field valuematchprovenance.author_type: team_member
Match one of several valuesmatch with arrayprovenance.author: [alice, bob]
Match any of several fieldsmatch_anyAuthor OR reviewer matches
Exclude specific valuesexcludeDrop bot authors
Match by regex patternmatch with regexpayload.cwd: '/\/work\//'
Parameterize with env varsmatch + CSV expansion${TEAM_MEMBERS}
Inspect array elementsjqLabels contain “needs-review”
Complex boolean logicjqAuthor AND label AND NOT draft
Transform the eventjqAdd computed fields

Rule of thumb: Start with match/exclude. If you find yourself needing to inspect array elements or combine complex boolean logic, switch to jq. jq is the escape hatch — it can express anything.

If no match, exclude, match_any, or jq is configured, the filter passes all events unchanged. This means you can add the filter to a pipeline and configure it later.

The filter is designed to be safe:

  • Invalid regex → falls back to exact string match
  • Missing jq binary → event dropped (logged as error)
  • jq timeout (5s) → event dropped (logged as error)
  • Missing dot-path segments → treated as undefined (no match, no crash)

The filter follows OrgLoop’s fail-open philosophy for match/exclude mode (missing fields just don’t match). jq mode fails closed (errors drop events) because jq errors typically indicate a logic problem that should be fixed.