GitHub Actions Expressions and Contexts
Expressions and contexts let you read dynamic information about your workflow run and make decisions based on it. They are the data access layer of GitHub Actions — the way you ask "who triggered this? which branch is this? did the last step succeed?" and use those answers inside your workflow.
What Is an Expression?
An expression is a formula you write inside ${{ }} double curly braces. GitHub evaluates it at runtime and replaces it with the result.
${{ github.actor }} → "alice"
${{ github.ref_name }} → "main"
${{ runner.os }} → "Linux"
${{ secrets.MY_KEY }} → "***" (masked secret value)
You can use expressions in almost any value field inside a workflow file — including env values, if conditions, step inputs, and job outputs.
What Is a Context?
A context is a set of related variables grouped under one name. Think of contexts like drawers in a filing cabinet. Each drawer (context) holds related documents (variables). You open a drawer by typing its name, then pull out a specific document using dot notation.
Filing Cabinet (GitHub Actions Runtime)
│
├── github drawer
│ ├── actor → "alice"
│ ├── ref_name → "main"
│ └── event_name → "push"
│
├── runner drawer
│ ├── os → "Linux"
│ └── temp → "/tmp"
│
├── secrets drawer
│ └── MY_SECRET → (encrypted, masked)
│
└── steps drawer
└── my-step-id
└── outputs
└── my-value → "hello"
The Most Useful Contexts
The github Context
Contains information about the event that triggered the workflow:
${{ github.actor }} → Username who triggered the run
${{ github.ref_name }} → Branch name (e.g. "main")
${{ github.sha }} → Full commit hash
${{ github.repository }} → "owner/repo-name"
${{ github.event_name }} → "push", "pull_request", etc.
${{ github.run_number }} → Sequential run count (1, 2, 3...)
${{ github.head_ref }} → Source branch of a pull request
${{ github.base_ref }} → Target branch of a pull request
The runner Context
Contains information about the machine running the job:
${{ runner.os }} → "Linux", "Windows", or "macOS"
${{ runner.arch }} → "X64", "ARM64"
${{ runner.temp }} → Path to a temporary directory
${{ runner.name }} → Name of the runner machine
The steps Context
Contains outputs and status from previous steps in the same job:
${{ steps.STEP_ID.outputs.OUTPUT_NAME }} → Value written to GITHUB_OUTPUT
${{ steps.STEP_ID.outcome }} → "success", "failure", "skipped"
${{ steps.STEP_ID.conclusion }} → Final result after continue-on-error
The needs Context
Contains outputs from jobs that the current job depends on:
${{ needs.JOB_NAME.outputs.OUTPUT_NAME }}
${{ needs.JOB_NAME.result }} → "success", "failure", "skipped"
Using Expressions in if Conditions
The most powerful use of expressions is inside if conditions on jobs and steps. An if condition controls whether a job or step runs at all.
steps:
- name: Deploy to production
if: ${{ github.ref_name == 'main' }}
run: ./deploy-prod.sh
- name: Deploy to staging
if: ${{ github.ref_name == 'develop' }}
run: ./deploy-staging.sh
The deploy-to-production step runs only on the main branch. The deploy-to-staging step runs only on the develop branch. All other branches skip both steps.
Status Functions in Expressions
GitHub provides special functions to check the status of previous steps:
Function | Returns true when
------------------|-----------------------------------
success() | All previous steps succeeded
failure() | Any previous step failed
always() | Always runs, regardless of status
cancelled() | Workflow was manually cancelled
steps:
- name: Run tests
run: npm test
- name: Send failure alert
if: ${{ failure() }}
run: echo "Tests failed! Alerting the team."
- name: Cleanup temp files
if: ${{ always() }}
run: rm -rf /tmp/build-cache
The cleanup step runs whether the tests pass or fail. The alert step runs only when something fails.
Operators in Expressions
Operator | Meaning | Example
----------|---------------|-------------------------------
== | Equals | github.actor == 'alice'
!= | Not equals | github.event_name != 'push'
&& | And | success() && github.ref_name == 'main'
|| | Or | failure() || cancelled()
! | Not | !cancelled()
> | Greater than | github.run_number > 10
The env Context
You can read environment variables you defined in the workflow using the env context inside expressions:
env:
DEPLOY_ENV: production
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check deploy target
if: ${{ env.DEPLOY_ENV == 'production' }}
run: echo "Deploying to production"
Practical Expression Example
This workflow deploys only when all of these conditions are true: the push is to the main branch, the actor is not a bot, and the previous build job succeeded.
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
deploy:
needs: build
runs-on: ubuntu-latest
if: |
${{ github.ref_name == 'main' &&
!contains(github.actor, '[bot]') &&
needs.build.result == 'success' }}
steps:
- run: ./deploy.sh
The pipe | after if: lets you write the expression across multiple lines for readability.
