Conditions and context data are how one workflow behaves differently for PRs, branches and failures.
An expression ${{ ... }} is evaluated by GitHub, not by the shell. It can read contexts, objects describing the run:
github: event name, ref, sha, actor, repository and the full webhook payload ingithub.event.env,vars,secrets,inputs: values you defined.steps.<id>.outputsandsteps.<id>.outcome: results of earlier steps (give the step anid).needs.<job>.outputsandneeds.<job>.result: results of jobs listed inneeds.matrix,runner,job.
Expressions support ==, !=, &&, ||, ! and functions such as contains(), startsWith(), format(), join(), toJSON(), fromJSON() and hashFiles(). String comparison is case-insensitive.
In if: keys the ${{ }} wrapper is optional. A step's if implicitly includes success(), meaning "all previous steps succeeded". The status functions change that:
failure(): run only if an earlier step failed (notifications, uploading logs).always(): run regardless, even after cancellation.cancelled(): run only when the run was cancelled.!cancelled(): run after success or failure but not cancellation, usually better thanalways()for cleanup.
Debugging tip: dump a context with echo '${{ toJSON(github.event) }}' to see exactly what you can use, but never do this with secrets.
Example
steps:
- id: tests
run: npm test
- if: failure() && steps.tests.outcome == 'failure'
run: echo "Tests failed on ${{ github.ref_name }}"
- if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'preview')
run: echo "Building preview for PR ${{ github.event.number }}"
- if: ${{ !cancelled() }}
run: echo "always tidy up (unless cancelled)"
- if: startsWith(github.ref, 'refs/tags/v')
run: echo "release ${{ github.ref_name }}" Remember
- Contexts expose the event, steps, needs, matrix and your variables
- if: implies success() unless you use a status function
- Prefer !cancelled() to always() for cleanup steps
Watch out for
- Writing if: ! startsWith(...) without ${{ }}: a leading ! is YAML syntax, so wrap it in ${{ }}.