GitHub Actions Tutorial — Expression Syntax

2 minute read

Expression Syntax

Expressions are written inside ${{ }} and can appear in most workflow YAML fields. They evaluate to a value — a string, number, boolean, or null.


Where Expressions Can Be Used


jobs:
  example:
    runs-on: ubuntu-latest
    if: ${{ github.ref == 'refs/heads/main' }}       # job conditional
    env:
      TAG: ${{ github.sha }}                          # environment variable
    steps:
      - run: echo "${{ github.actor }}"               # inline in run
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.pull_request.head.sha }}  # action input

Note: In if: conditions, the ${{ }} wrapper is optional — GitHub evaluates the expression automatically:


# Both are equivalent
if: ${{ github.ref == 'refs/heads/main' }}
if: github.ref == 'refs/heads/main'


Literals

Type Examples
Boolean true, false
Null null
Number 42, 3.14, -1
String 'hello' (single quotes only)

Strings must use single quotes inside expressions:


if: github.event_name == 'push'         # ✅ correct
if: github.event_name == "push"         # ❌ won't work


Operators

Comparison Operators

Operator Meaning Example
== Equal github.ref == 'refs/heads/main'
!= Not equal github.actor != 'dependabot[bot]'
< Less than github.run_number < 100
> Greater than steps.test.outputs.count > 0
<= Less or equal github.run_attempt <= 3
>= Greater or equal needs.build.outputs.version >= '2.0'

Logical Operators

Operator Meaning Example
&& AND github.ref == 'refs/heads/main' && github.event_name == 'push'
\|\| OR github.event_name == 'push' \|\| github.event_name == 'workflow_dispatch'
! NOT !github.event.pull_request.draft

Grouping


if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'


Type Coercion

GitHub Actions automatically converts types in comparisons:

Comparison Result
null == '' true
null == 0 true
null == false true
'' == 0 true
'true' == true true (for if: specifically)
'0' == false true

This can cause surprises. Be explicit when checking for empty strings vs null:


# Checking if output is set (non-empty)
if: steps.check.outputs.value != ''

# Checking if output is truly empty
if: steps.check.outputs.value == ''


Property Access

Access nested properties with dot notation or bracket notation:


# Dot notation
${{ github.event.pull_request.head.ref }}

# Bracket notation (required for special characters)
${{ github.event['pull_request']['head']['ref'] }}

# Dynamic property access
${{ needs[env.JOB_NAME].outputs.result }}


Common Mistakes

Mistake Fix
Double quotes in expressions Use single quotes: 'value' not "value"
Missing ${{ }} in env: Required in env: and with:, optional in if:
Type coercion surprises null == '' is true — be explicit in checks
Comparing numbers as strings Outputs are always strings; use fromJSON() to convert

Quick Reference

Category Syntax
Expression wrapper ${{ expression }}
String literal 'single quotes'
Comparison ==, !=, <, >, <=, >=
Logical &&, \|\|, !
Property access object.property or object['property']
Implicit in if: ${{ }} is optional in if:

← Back to: Chapter 7: Expressions

Next: Contexts →

Updated: