Overview
Pipelines-as-Code uses annotations on PipelineRuns to determine which pipelines should run for specific Git events. This powerful matching system supports simple branch/event matching, file path patterns, labels, and advanced CEL expressions.
Basic Event Matching
At minimum, specify which events and branches trigger your pipeline:
metadata :
name : pr-pipeline
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
This runs on pull requests targeting the main branch.
Event Types
pipelinesascode.tekton.dev/on-event
The Git event type that triggers the pipeline Values : pull_request, push, incomingannotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
# Multiple events
pipelinesascode.tekton.dev/on-event : "[pull_request, push]"
pull_request
push
incoming
Triggers when:
Pull request opened
Pull request updated (new commits)
Pull request synchronized
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
Triggers when:
Commits pushed to a branch
Tags pushed to repository
annotations :
pipelinesascode.tekton.dev/on-event : "[push]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
Triggers from:
Incoming webhooks (external triggers)
annotations :
pipelinesascode.tekton.dev/on-event : "[incoming]"
Branch Matching
pipelinesascode.tekton.dev/on-target-branch
The branch(es) that trigger the pipeline annotations :
# Single branch
pipelinesascode.tekton.dev/on-target-branch : "[main]"
# Multiple branches
pipelinesascode.tekton.dev/on-target-branch : "[main, develop, staging]"
# Glob patterns
pipelinesascode.tekton.dev/on-target-branch : "[main, release-*]"
Short vs Full Refs
Both formats are supported:
# Short ref
pipelinesascode.tekton.dev/on-target-branch : "[main]"
# Full ref
pipelinesascode.tekton.dev/on-target-branch : "[refs/heads/main]"
Glob Patterns
All Branches
Release Branches
Feature Branches
Version Tags
annotations :
pipelinesascode.tekton.dev/on-target-branch : "[refs/heads/*]"
Tag Matching Example
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : tag-release
annotations :
pipelinesascode.tekton.dev/on-event : "[push]"
pipelinesascode.tekton.dev/on-target-branch : "[refs/tags/v*]"
spec :
params :
- name : git-tag
value : "{{ git_tag }}" # Available for tag events
pipelineSpec :
params :
- name : git-tag
tasks :
- name : create-release
taskSpec :
params :
- name : tag
steps :
- name : release
image : alpine
script : |
echo "Creating release for tag $(params.tag)"
params :
- name : tag
value : "$(params.git-tag)"
GitHub doesn’t send webhook events when more than 3 tags are pushed simultaneously (e.g., git push origin --tags). Push tags in batches of 3 or fewer.
Branch with Comma
If a branch name contains a comma, use HTML entity ,:
annotations :
pipelinesascode.tekton.dev/on-target-branch : "[main, release,nightly]"
This matches main and release,nightly.
File Path Matching
Trigger pipelines based on which files changed in the event.
pipelinesascode.tekton.dev/on-path-change
Trigger when files matching the pattern change annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-path-change : "[src/**, tests/**]"
Uses glob patterns
pipelinesascode.tekton.dev/on-path-change-ignore
Trigger when files NOT matching the pattern change annotations :
pipelinesascode.tekton.dev/on-path-change-ignore : "[docs/**, **.md]"
Examples
Documentation Only
Ignore Documentation
Go Files Only
Combined Patterns
metadata :
name : docs-validation
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-path-change : "[docs/**, **.md]"
on-path-change-ignore takes precedence over on-path-change. If a file matches both, it’s ignored.
Test Glob Patterns
Use the tkn pac CLI to test glob patterns:
tkn pac info globbing "[src/**, tests/**]"
This shows which files in the current directory match the pattern.
Label Matching
Trigger pipelines based on pull request labels.
pipelinesascode.tekton.dev/on-label
Trigger when PR has specific labels annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-label : "[bug, defect]"
Supported : GitHub, GitLab, Forgejo
Behavior
Triggers immediately when label is added
No other matching PipelineRuns are triggered
Triggers again on PR updates if label still present
Access labels via {{ pull_request_labels }} variable (newline-separated)
Example
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : bug-triage
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-label : "[bug, critical]"
spec :
pipelineSpec :
tasks :
- name : notify-team
taskSpec :
steps :
- name : print-labels
image : alpine
script : |
#!/bin/sh
echo "PR has labels:"
for label in $(echo -e "{{ pull_request_labels }}"); do
echo " - $label"
done
Trigger pipelines when a comment matches a regex pattern.
Trigger on matching comment (regex) annotations :
pipelinesascode.tekton.dev/on-comment : "^/deploy"
Supported : GitHub, GitLab, Forgejo (pull requests and pushed commits)
Regex Matching
Spaces and newlines stripped from beginning/end
^ matches comment start
$ matches comment end
Only new comments trigger (not edits)
Examples
Deploy Command
Merge PR Command
Advanced Pattern
metadata :
name : deploy-staging
annotations :
pipelinesascode.tekton.dev/on-comment : "^/deploy-staging"
The triggering comment is available as {{ trigger_comment }}:
params :
- name : comment
value : "{{ trigger_comment }}"
Newlines are replaced with \n. Restore them in scripts:
echo -e "{{ trigger_comment }}" > /tmp/comment
See GitOps Commands - Custom Commands for details.
Advanced CEL Expressions
For complex matching logic, use CEL (Common Expression Language).
pipelinesascode.tekton.dev/on-cel-expression
CEL expression for advanced matching annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" && target_branch == "main" && source_branch.startsWith("feature/")
CEL expressions override all other annotations (on-event, on-target-branch, on-label, on-path-change). Use CEL alone or use the simple annotations.
Available Variables
Variable Type Description Example eventstring Normalized event type pull_request, push, incomingevent_typestring Provider-specific event pull_request (GitHub), Merge Request (GitLab)target_branchstring Target branch mainsource_branchstring Source branch (PR head) feature/my-featuretarget_urlstring Target repository URL https://github.com/org/reposource_urlstring Source repository URL (fork) https://github.com/user/repoevent_titlestring Event title (PR title or commit title) Fix bug in parserbodyobject Full webhook payload body.pull_request.numberheadersmap HTTP headers (lowercase) headers['x-github-event']filesobject Changed files files.all, files.added, files.modified, files.deleted, files.renamed
CEL Examples
Exclude Branch
Regex Branch Match
Multiple Conditions
Specific Author
Event Title Match
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" && target_branch != "experimental"
File Matching with CEL
CEL provides two methods for file matching:
Method 1: .pathChanged() (Glob)
Use glob patterns (all change types):
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" && "docs/*.md".pathChanged()
Method 2: files. Properties (Regex + Change Type)
Target specific change types with regex:
Any Changed Files
Added Files Only
Modified Go Files
Exclude Non-Code Files
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
files.all.exists(x, x.matches('tmp/'))
In CEL regex, escape special characters with \\. For example, \.go$ becomes \\.go$ in the CEL string.
Accessing Webhook Payload
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
body.pull_request.base.ref == "main" &&
body.pull_request.user.login == "admin" &&
body.action == "synchronize"
GitHub payload fields :
body.pull_request.number
body.pull_request.title
body.pull_request.user.login
body.pull_request.draft
body.pull_request.base.ref (target branch)
body.pull_request.head.ref (source branch)
body.action (opened, synchronize, etc.)
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
headers['x-github-event'] == "pull_request"
Headers are lowercase and provider-specific.
Custom Parameters in CEL
Repository CR custom parameters are available in CEL:
apiVersion : "pipelinesascode.tekton.dev/v1alpha1"
kind : Repository
spec :
params :
- name : enable_ci
value : "true"
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
enable_ci == "true" && event == "pull_request"
Filtered parameters : If a parameter has a filter that doesn’t match, it’s undefined in CEL, causing evaluation errors.spec :
params :
- name : docker_registry
value : "registry.staging.com"
filter : 'event == "pull_request"'
On a push event, docker_registry is undefined. CEL expression docker_registry == "registry.staging.com" produces an error, not false. Solution : Only reference filtered parameters when the filter matches, or use unfiltered parameters.
Test CEL Expressions
Test CEL expressions locally:
tkn pac cel --expression 'event == "pull_request" && target_branch == "main"' \
--event pull_request \
--target-branch main
Multiple PipelineRuns
When multiple PipelineRuns match an event, they run in parallel :
# .tekton/build.yaml
metadata :
name : build
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
# .tekton/test.yaml
metadata :
name : test
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
# .tekton/lint.yaml
metadata :
name : lint
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
All three run simultaneously (unless concurrency_limit is set in the Repository CR).
Complete Examples
Monorepo Service-Specific Pipelines
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : service-a-ci
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-path-change : "[service-a/**, shared/**]"
spec :
pipelineSpec :
tasks :
- name : build-service-a
# ...
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : service-b-ci
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
pipelinesascode.tekton.dev/on-path-change : "[service-b/**, shared/**]"
spec :
pipelineSpec :
tasks :
- name : build-service-b
# ...
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : release
annotations :
pipelinesascode.tekton.dev/on-event : "[push]"
pipelinesascode.tekton.dev/on-target-branch : "[refs/tags/v*.*.*]"
spec :
params :
- name : tag
value : "{{ git_tag }}"
pipelineSpec :
params :
- name : tag
tasks :
- name : create-github-release
# ...
- name : publish-artifacts
# ...
Skip Draft PRs
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : pr-ci
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" &&
target_branch == "main" &&
!body.pull_request.draft
spec :
# ...
Dependabot Auto-Merge
.tekton/dependabot-auto-merge.yaml
apiVersion : tekton.dev/v1beta1
kind : PipelineRun
metadata :
name : dependabot-auto-merge
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" &&
body.pull_request.user.login == "dependabot[bot]" &&
event_title.startsWith("Bump")
spec :
pipelineSpec :
tasks :
- name : verify-tests-passed
# ...
- name : auto-merge
# ...
Best Practices
Use simple annotations when possible
Prefer on-event and on-target-branch over CEL for basic matching:
# ✅ Good (simple, readable)
annotations :
pipelinesascode.tekton.dev/on-event : "[pull_request]"
pipelinesascode.tekton.dev/on-target-branch : "[main]"
# ❌ Overkill (unnecessary complexity)
annotations :
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" && target_branch == "main"
Test glob patterns locally
tkn pac info globbing "[src/**, tests/**]"
tkn pac cel \
--expression 'event == "pull_request" && files.all.exists(x, x.matches("\\.go$"))' \
--event pull_request
Document complex CEL expressions
Add comments explaining complex logic:
annotations :
# Only run on PRs to main from feature branches
# Skip draft PRs and docs-only changes
pipelinesascode.tekton.dev/on-cel-expression : |
event == "pull_request" &&
target_branch == "main" &&
source_branch.startsWith("feature/") &&
!body.pull_request.draft &&
!files.all.all(x, x.matches('^docs/'))
Avoid filtering on body payload with GitOps commands
GitOps commands (e.g., /retest) change the payload to the comment event, breaking body-based CEL expressions.
Instead, use /test <pipelinerun-name> or update the PR with:
git commit --amend --no-edit && git push --force-with-lease
Next Steps
Creating Pipelines Author PipelineRuns with dynamic variables
GitOps Commands Trigger pipelines with /test, /retest, and custom commands
Running Pipelines Understand execution and authorization
Repository CRD Configure the Repository Custom Resource