GitHub Actions Tutorial — Job Outputs

2 minute read

Job Outputs

Since each job runs on a separate runner (fresh VM), there’s no shared filesystem between jobs. To pass data from one job to another, you use job outputs — values set in one job and read by downstream jobs via the needs context.


How Job Outputs Work

Job A (producer)                    Job B (consumer)
┌──────────────────┐               ┌──────────────────┐
│ Step: set output │               │ Read output via   │
│   → version=2.1  │──── needs ───►│   needs.A.outputs │
│                  │               │   .version        │
│ outputs:         │               │                   │
│   version: ...   │               │                   │
└──────────────────┘               └──────────────────┘

Step 1 — Set a Step Output

Inside a step, write to $GITHUB_OUTPUT:


steps:
  - name: Set version
    id: version-step        # ← step ID (required to reference later)
    run: echo "version=2.1.0" >> "$GITHUB_OUTPUT"


Step 2 — Map Step Output to Job Output

In the job’s outputs: section, map the step output to a job output:


jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version-step.outputs.version }}
    steps:
      - name: Set version
        id: version-step
        run: echo "version=2.1.0" >> "$GITHUB_OUTPUT"


Step 3 — Read in a Downstream Job

The downstream job reads the output via needs.<job-id>.outputs.<name>:


jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version-step.outputs.version }}
    steps:
      - name: Set version
        id: version-step
        run: echo "version=2.1.0" >> "$GITHUB_OUTPUT"

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Use version
        run: echo "Deploying version ${{ needs.build.outputs.version }}"

Output:

Deploying version 2.1.0

Complete Example — Dynamic Version


name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.pkg.outputs.version }}
      sha-short: ${{ steps.sha.outputs.sha }}
    steps:
      - uses: actions/checkout@v4

      - name: Get package version
        id: pkg
        run: echo "version=$(node -p 'require(\"./package.json\").version')" >> "$GITHUB_OUTPUT"

      - name: Get short SHA
        id: sha
        run: echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"

      - name: Build
        run: echo "Building version ${{ steps.pkg.outputs.version }}"

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        run: |
          echo "Version: ${{ needs.build.outputs.version }}"
          echo "Commit: ${{ needs.build.outputs.sha-short }}"


Multiple Outputs

A job can have multiple outputs:


jobs:
  analyze:
    runs-on: ubuntu-latest
    outputs:
      should-deploy: ${{ steps.check.outputs.deploy }}
      environment: ${{ steps.check.outputs.env }}
      version: ${{ steps.check.outputs.version }}
    steps:
      - name: Determine deployment
        id: check
        run: |
          echo "deploy=true" >> "$GITHUB_OUTPUT"
          echo "env=production" >> "$GITHUB_OUTPUT"
          echo "version=3.0.0" >> "$GITHUB_OUTPUT"

  deploy:
    needs: analyze
    if: needs.analyze.outputs.should-deploy == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Deploying ${{ needs.analyze.outputs.version }}"
          echo "Environment: ${{ needs.analyze.outputs.environment }}"


Common Mistakes

Mistake Fix
Output is empty in downstream job Make sure the step has an id: and the job has outputs: mapping
Using set-output command Deprecated. Use echo "key=value" >> "$GITHUB_OUTPUT" instead
Output value contains newlines Outputs are single-line. For multi-line data, use artifacts instead
Missing needs: on consumer job The consumer must declare needs: <producer-job>

Quick Reference

Concept Syntax
Set step output echo "key=value" >> "$GITHUB_OUTPUT"
Step ID id: my-step
Map to job output outputs: name: ${{ steps.my-step.outputs.key }}
Read in next job ${{ needs.job-id.outputs.name }}
Requires needs: job-id on the consuming job

← Previous: Job Dependencies

Next: Container Jobs →

Updated: