GitHub·8 min read·

GitHub Actions Context and Expressions Explained

Master the GitHub Actions github, secrets, inputs, matrix and needs contexts, plus the expression functions that power every if condition and output.

NB

Netbay Cloud Team

Netbay Engineering

On this page

Every value your workflow can read — the branch it ran on, the author of the commit, a secret, an input, a matrix value, a previous job's result — lives somewhere in GitHub Actions' context object model. Master these named contexts and the small expression language that reads them, and workflows stop being guesswork. This post maps the contexts you will actually use and shows the syntax for reading and transforming their values.

The expression syntax

GitHub expressions are written with the dollar-double-brace form: in the workflow file they appear as ${{ expression }}, where inside the braces you write a pure expression — property access, function calls, and operators. The parser evaluates the expression only at runtime; the expression itself never directly runs shell code. That separation is a key safety property. Still, never place untrusted raw input into a run line without quoting, because that line does reach the shell.

Expressions support simple operators (==, !=, &&, ||, !), literals (booleans, numbers, strings), and a library of functions like contains, startsWith, endsWith, format, join, and github object access.

yaml
steps:
  - name: Check branch
    if: github.ref == 'refs/heads/main' && success()
    run: echo on main
  - name: Name the build
    run: echo "Building ${{ github.sha }}"

The github context

The github context is the richest and most frequently read. It holds metadata about the current event and run: github.repository, github.ref, github.sha, github.actor (who triggered it), github.event_name, github.workflow, and github.run_id. Under github.event you get the full payload of the triggering webhook, so a pull_request event exposes github.event.pull_request.number, its title, its base branch and more.

These values drive most if conditions and label build artifacts. Use github.repository to name artifacts, github.sha (shortened via github.sha.substr(0,7)) for unique build tags, and github.actor to fetch per-author secrets if needed.

The secrets, inputs and env contexts

The secrets context holds every secret available to the run. Reference them as secrets.NAME. They are never echoed to logs when used in env blocks, but if you put a secret into a run line GitHub masks its value in the logs. The inputs context holds workflow_dispatch inputs (inputs.NAME). The env context holds environment variables set with the env key or from a matrix-provided environment — read them as env.NAME within steps.

Secrets and inputs differ in one security-relevant way: secrets are masked in logs and cannot be passed to run in ways that expose them; inputs are visible and should be treated as untrusted.

yaml
on:
  workflow_dispatch:
    inputs:
      target:
        description: where to deploy
        required: true
jobs:
  deploy:
    runs-on: ubuntu-22.04
    steps:
      - name: Echo target
        run: echo "target is ${{ inputs.target }}"
      - name: Use a secret
        env:
          API_KEY: ${{ secrets.DEPLOY_KEY }}
        run: ./deploy.sh --key "$API_KEY"

The matrix, needs and jobs contexts

Inside a matrix job, the matrix context holds the current combination (matrix.os, matrix.node). The needs context lets a job read outputs from jobs it depends on, via needs.<job>.outputs.<name>. That is the standard mechanism for passing data between sequential jobs. The jobs context provides the result and outputs of all jobs that ran before.

To pass output between jobs, the producing job declares outputs and sets them with set-output (modern syntax uses echo "name=value" >> "$GITHUB_OUTPUT"), then the consumer reads needs:

yaml
jobs:
  build:
    runs-on: ubuntu-22.04
    outputs:
      tag: ${{ steps.tagstep.outputs.tag }}
    steps:
      - id: tagstep
        run: echo "tag=v1-${{ github.run_number }}" >> "$GITHUB_OUTPUT"
  deploy:
    runs-on: ubuntu-22.04
    needs: build
    steps:
      - run: echo "deploying ${{ needs.build.outputs.tag }}"

Functions worth knowing

You will rely on a handful of functions. Use contains() to check if a string or array contains a value (it also works on branch names). Use startsWith/endsWith for ref patterns. Use format() to build strings with placeholders. Use always(), success(), failure(), and cancelled() for status logic. When comparing, remember null behaves as an empty string in many comparisons.

Two traps: a missing context returns null (renders as empty), and error()/failure() helpers force the workflow to halt or mark a step failed respectively. Use error() to stop early when a precondition is violated, which is far cleaner than chaining if conditions.

github secrets / inputs matrix / needs / env ${{ expr }} evaluated at run time functions contains, startsWith, format if: github.ref == ... && success() read value -> transform -> gate job/step

Takeaway

The contexts are the object model of a run: github for event metadata, secrets/inputs for configuration, matrix/needs for orchestrating parallel and sequential work. Learn the ${'/'} expression syntax and the top functions, and you can write conditions, name artifacts, and pass data between jobs with confidence. To give those workflows a real deploy target, a Netbay Ubuntu VPS is ready in under a minute — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance