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.

Leave a Comment

Your email address will not be published. Required fields are marked *