Tekton’s whenExpressions can make your pipelines feel like they’re thinking, but most folks use them to just guard against the obvious.
Let’s see it in action. Imagine a pipeline that only deploys to production if a manual approval step has been explicitly passed.
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: conditional-deployment
spec:
params:
- name: deploy-to-production
type: string
description: Set to "true" to deploy to production.
default: "false"
tasks:
- name: build-and-test
taskRef:
name: build-and-test-task
- name: approve-production-deployment
taskRef:
name: manual-approval-task
when: # This task runs only if the previous one (build-and-test) succeeded.
- input: $(tasks.build-and-test.status)
operator: in
values: ["succeeded"]
- name: deploy-production
taskRef:
name: deploy-production-task
when:
- input: $(params.deploy-to-production) # This task runs only if the pipeline param is "true".
operator: in
values: ["true"]
- input: $(tasks.approve-production-deployment.status) # AND only if the approval task succeeded.
operator: in
values: ["succeeded"]
Here, build-and-test-task always runs. The approve-production-deployment task only runs if build-and-test succeeded, because we’re checking its status. The deploy-production task has two conditions: the deploy-to-production pipeline parameter must be set to "true", and the approve-production-deployment task must have succeeded. If either of these conditions for deploy-production isn’t met, that task is skipped.
The problem whenExpressions solve is reducing pipeline complexity and preventing unintended actions. Instead of having separate pipelines for different deployment targets (e.g., staging vs. production) or relying on external logic to decide which pipeline to run, you can embed that decision-making directly into a single pipeline definition. This makes your CI/CD process more declarative and easier to manage.
Internally, Tekton evaluates whenExpressions before a task is scheduled for execution. For each task that has a when block, Tekton checks every condition listed. If all conditions evaluate to true, the task is allowed to proceed. If any condition evaluates to false, the task is marked as skipped and its subsequent tasks that depend on it will also likely be skipped (unless they have their own independent when conditions). The results and status fields from previously run tasks, as well as params passed to the pipeline, are the primary sources of data for these expressions.
You can string together multiple conditions using and logic implicitly by listing them under the same when key. For instance, you could require a specific Git branch and a successful test run before deploying. The operator field is crucial here: in checks for membership in a list, notin checks for absence, exists checks if a value is present, nexists checks if it’s absent, eq for equality, neq for inequality, gt for greater than, gte for greater than or equal to, lt for less than, and lte for less than or equal to. These operators work on strings, numbers, and booleans.
It’s easy to think that whenExpressions are just for simple true/false checks. However, you can perform quite sophisticated logic. For example, you might want to deploy to a staging environment unless a specific Git tag (like v1.0.0-rc1) is present, in which case you’d skip staging and go straight to production. This would involve checking the Git tag value against a list of "release candidate" tags.
- name: deploy-staging
taskRef:
name: deploy-task
params:
- name: environment
value: "staging"
when:
- input: $(params.git-tag)
operator: notin
values: ["v1.0.0-rc1", "v1.0.0-rc2"] # Skip staging if it's a release candidate
The next logical step after mastering conditional execution is understanding how to dynamically generate tasks or their parameters based on preceding task outputs.