GitHub Actions Tutorial — Conditionals

2 minute read

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 →

Updated: