GitHub Actions Conditional Steps
Conditional steps let you run specific parts of your workflow only when certain conditions are true. Instead of creating separate workflow files for different situations, you add simple rules that decide whether each step or job should execute. This makes your workflows flexible, efficient, and easier to maintain.
The if Keyword
The if keyword works on both jobs and steps. When the condition evaluates to true, the job or step runs. When it evaluates to false, it is skipped without causing a failure.
steps:
- name: Run only on main branch
if: github.ref_name == 'main'
run: echo "This is the main branch"
Notice that inside if conditions, you can write expressions with or without the ${{ }} wrapper — GitHub treats the entire value as an expression automatically. Both of these work:
if: github.ref_name == 'main'
if: ${{ github.ref_name == 'main' }}
Conditional Jobs vs Conditional Steps
You can apply if conditions to both jobs and individual steps. The effect is the same — skip when the condition is false — but the scope differs:
Conditional job — the entire job is skipped:
jobs:
deploy:
if: github.ref_name == 'main'
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
Conditional step — only that step is skipped:
steps:
- name: Send Slack alert
if: failure()
run: curl -X POST "${{ secrets.SLACK_WEBHOOK }}"
Branch-Based Conditions
A common pattern is running different commands depending on which branch triggered the workflow:
steps:
- name: Deploy to production
if: github.ref_name == 'main'
run: ./scripts/deploy-prod.sh
- name: Deploy to staging
if: github.ref_name == 'staging'
run: ./scripts/deploy-staging.sh
- name: Run preview deployment
if: startsWith(github.ref_name, 'feature/')
run: ./scripts/deploy-preview.sh
Branch diagram:
main branch push → deploy-prod step runs
staging branch push → deploy-staging step runs
feature/login push → deploy-preview step runs
Event-Based Conditions
Run a step only when a specific event triggers the workflow:
steps:
- name: Comment on pull request
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Tests are running!"
})
Status-Based Conditions
The most common conditional pattern handles failures gracefully. GitHub provides built-in status functions:
steps:
- name: Run tests
id: tests
run: npm test
- name: Upload test logs on failure
if: failure()
run: ./upload-logs.sh
- name: Notify team on success
if: success()
run: echo "All tests passed!"
- name: Archive results regardless
if: always()
run: ./archive-results.sh
Flow when tests pass:
Run tests ✓ → skip "Upload logs" → "Notify team" ✓ → "Archive" ✓
Flow when tests fail:
Run tests ✗ → "Upload logs" ✓ → skip "Notify team" → "Archive" ✓
Combining Multiple Conditions
Combine conditions using && (and) and || (or) operators:
steps:
- name: Deploy to production
if: github.ref_name == 'main' && github.event_name == 'push'
run: ./deploy.sh
- name: Alert on any failure or cancellation
if: failure() || cancelled()
run: ./alert.sh
Checking Who Triggered the Workflow
Run a step only when a specific user or role triggered the workflow:
steps:
- name: Run admin task
if: github.actor == 'alice'
run: ./admin-task.sh
- name: Skip bot-triggered steps
if: "!contains(github.actor, '[bot]')"
run: ./notify-team.sh
The second example skips the notification step when an automated bot (like Dependabot) triggers the workflow. Bots typically have [bot] in their username.
Checking Job Dependencies in Conditions
When jobs depend on each other with needs, use the needs context in conditions to check a previous job's outcome:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
notify:
needs: build
runs-on: ubuntu-latest
if: needs.build.result == 'failure'
steps:
- run: echo "Build failed! Sending alert."
Using Outputs in Conditions
A step can write a value and a later step checks that value before deciding to run:
steps:
- name: Check if tests changed
id: check
run: |
if git diff --name-only HEAD~1 | grep -q 'test/'; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
- name: Run tests
if: steps.check.outputs.changed == 'true'
run: npm test
The test step only runs when test files actually changed. This saves time on commits that only update documentation or configuration files.
Condition Evaluation Summary
Condition | When it runs
-----------------------------------|-----------------------------
github.ref_name == 'main' | Push to main branch only
github.event_name == 'push' | Push events only
success() | Previous steps all passed
failure() | At least one step failed
always() | Every time, no exceptions
cancelled() | Workflow was cancelled
contains(github.actor, '[bot]') | A bot triggered the workflow
startsWith(github.ref, 'refs/tags')| A tag was pushed
