GitHub Actions Tutorial — Conditionals
Conditionals
The if: keyword controls whether a step or job runs. It accepts an expression that evaluates to true or false.
Step-Level Conditionals
steps:
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: ./deploy.sh production
- name: Deploy to staging
if: github.ref == 'refs/heads/develop'
run: ./deploy.sh staging
Job-Level Conditionals
jobs:
deploy:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- run: echo "Deploying..."
Common Patterns
Run on Specific Branch
if: github.ref == 'refs/heads/main'
Run on Push Only (Not PR)
if: github.event_name == 'push'
Skip for Bot Actors
if: github.actor != 'dependabot[bot]'
Run on Non-Draft PRs
if: github.event.pull_request.draft == false
Run Based on Changed Files
steps:
- id: changes
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'src/backend/**'
- if: steps.changes.outputs.backend == 'true'
run: echo "Backend changed — running backend tests"
Run Based on Commit Message
if: "!contains(github.event.head_commit.message, '[skip ci]')"
Run Based on Labels
if: contains(github.event.pull_request.labels.*.name, 'deploy')
Combining Conditions
# AND — both must be true
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
# OR — either can be true
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
# NOT — negate
if: "!github.event.pull_request.draft"
# Complex
if: |
(github.event_name == 'push' && github.ref == 'refs/heads/main') ||
github.event_name == 'workflow_dispatch'
Truthy and Falsy Values
GitHub Actions evaluates these as false:
| Value | Treated as |
|---|---|
false |
false |
0 |
false |
'' (empty string) |
false |
null |
false |
Everything else is true, including:
'false'(the string “false”) → true'0'(the string “0”) → true
Warning: Step outputs are always strings. steps.check.outputs.result might be 'false' (string), which is truthy:
# ❌ This ALWAYS runs because 'false' (string) is truthy
if: steps.check.outputs.should-deploy
# ✅ Compare as string
if: steps.check.outputs.should-deploy == 'true'
Conditional with Previous Step Outcome
steps:
- name: Test
id: test
continue-on-error: true
run: npm test
- name: On success
if: steps.test.outcome == 'success'
run: echo "Tests passed!"
- name: On failure
if: steps.test.outcome == 'failure'
run: echo "Tests failed — collecting diagnostics..."
Common Mistakes
| Mistake | Fix |
|---|---|
if: 'false' still runs |
String 'false' is truthy. Compare: if: var == 'true' |
Multi-line if: syntax error |
Use \| for multi-line: if: \| then expression on next line |
! at start of YAML value |
Wrap in quotes: if: "!condition" |
| Checking output as boolean | Outputs are strings; compare with == 'true' |
Quick Reference
| Pattern | Expression |
|---|---|
| Branch check | github.ref == 'refs/heads/main' |
| Event check | github.event_name == 'push' |
| Skip bot | github.actor != 'dependabot[bot]' |
| Step outcome | steps.id.outcome == 'success' |
| Job result | needs.job.result == 'success' |
| Contains | contains(github.event.*.labels.*.name, 'deploy') |
| Always run | always() |
← Previous: Contexts
Next: Status Functions →