Skip to main content

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
string
required
The Git event type that triggers the pipelineValues: pull_request, push, incoming
annotations:
  pipelinesascode.tekton.dev/on-event: "[pull_request]"
  # Multiple events
  pipelinesascode.tekton.dev/on-event: "[pull_request, push]"
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]"

Branch Matching

pipelinesascode.tekton.dev/on-target-branch
string
required
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

annotations:
  pipelinesascode.tekton.dev/on-target-branch: "[refs/heads/*]"

Tag Matching Example

.tekton/tag-release.yaml
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
string
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
string
Trigger when files NOT matching the pattern change
annotations:
  pipelinesascode.tekton.dev/on-path-change-ignore: "[docs/**, **.md]"

Examples

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
string
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

.tekton/bug-triage.yaml
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

Comment-based Triggers

Trigger pipelines when a comment matches a regex pattern.
pipelinesascode.tekton.dev/on-comment
string
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

metadata:
  name: deploy-staging
  annotations:
    pipelinesascode.tekton.dev/on-comment: "^/deploy-staging"

Access Comment Content

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
string
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

VariableTypeDescriptionExample
eventstringNormalized event typepull_request, push, incoming
event_typestringProvider-specific eventpull_request (GitHub), Merge Request (GitLab)
target_branchstringTarget branchmain
source_branchstringSource branch (PR head)feature/my-feature
target_urlstringTarget repository URLhttps://github.com/org/repo
source_urlstringSource repository URL (fork)https://github.com/user/repo
event_titlestringEvent title (PR title or commit title)Fix bug in parser
bodyobjectFull webhook payloadbody.pull_request.number
headersmapHTTP headers (lowercase)headers['x-github-event']
filesobjectChanged filesfiles.all, files.added, files.modified, files.deleted, files.renamed

CEL Examples

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:
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.)

Accessing Headers

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

.tekton/service-a.yaml
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
        # ...
.tekton/service-b.yaml
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
        # ...

Release Pipeline (Tags)

.tekton/release.yaml
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

.tekton/pr.yaml
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

1
Use simple annotations when possible
2
Prefer on-event and on-target-branch over CEL for basic matching:
3
# ✅ 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"
4
Test glob patterns locally
5
tkn pac info globbing "[src/**, tests/**]"
6
Test CEL expressions
7
tkn pac cel \
  --expression 'event == "pull_request" && files.all.exists(x, x.matches("\\.go$"))' \
  --event pull_request
8
Document complex CEL expressions
9
Add comments explaining complex logic:
10
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/'))
11
Avoid filtering on body payload with GitOps commands
12
GitOps commands (e.g., /retest) change the payload to the comment event, breaking body-based CEL expressions.
13
Instead, use /test <pipelinerun-name> or update the PR with:
14
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

Build docs developers (and LLMs) love