GitHub Actions Tutorial — Step Outputs

2 minute read

Step Outputs

Steps within the same job share a runner, so you can pass data between them using step outputs ($GITHUB_OUTPUT) and environment files ($GITHUB_ENV). These mechanisms replace the deprecated set-output and set-env commands.


GITHUB_OUTPUT — Step Outputs

Write key-value pairs to $GITHUB_OUTPUT to create step outputs:


steps:
  - name: Set output
    id: my-step
    run: echo "version=2.1.0" >> "$GITHUB_OUTPUT"

  - name: Use output
    run: echo "Version is ${{ steps.my-step.outputs.version }}"

Multiple Outputs


steps:
  - name: Set multiple outputs
    id: info
    run: |
      echo "version=2.1.0" >> "$GITHUB_OUTPUT"
      echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
      echo "branch=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"

  - name: Use all outputs
    run: |
      echo "Version: ${{ steps.info.outputs.version }}"
      echo "SHA: ${{ steps.info.outputs.sha }}"
      echo "Branch: ${{ steps.info.outputs.branch }}"


GITHUB_ENV — Persistent Environment Variables

Variables set with $GITHUB_ENV are available in all subsequent steps in the same job:


steps:
  - name: Set env var
    run: echo "MY_VERSION=2.1.0" >> "$GITHUB_ENV"

  - name: Use env var (next step)
    run: echo "Version is $MY_VERSION"

  - name: Use in expression
    run: echo "Also available as ${{ env.MY_VERSION }}"

GITHUB_OUTPUT vs GITHUB_ENV

Feature $GITHUB_OUTPUT $GITHUB_ENV
Scope Step-to-step (via expressions) All subsequent steps
Access ${{ steps.id.outputs.key }} $KEY or ${{ env.KEY }}
Use case Structured data, conditionals Env vars for commands
Cross-job Yes (via job outputs) No (same job only)

Rule of thumb:

  • Use $GITHUB_OUTPUT for data you’ll use in if: conditions or pass to other jobs
  • Use $GITHUB_ENV for environment variables your shell commands need

GITHUB_PATH — Add to PATH

Add directories to the system PATH for subsequent steps:

steps:
  - name: Install custom tool
    run: |
      mkdir -p "$HOME/my-tools"
      cp my-binary "$HOME/my-tools/"
      echo "$HOME/my-tools" >> "$GITHUB_PATH"

  - name: Use custom tool
    run: my-binary --version     # now available in PATH

Multi-Line Outputs

For values containing newlines, use a delimiter:


steps:
  - name: Set multi-line output
    id: changelog
    run: |
      EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
      echo "content<<$EOF" >> "$GITHUB_OUTPUT"
      echo "Line 1: Bug fixes" >> "$GITHUB_OUTPUT"
      echo "Line 2: New features" >> "$GITHUB_OUTPUT"
      echo "$EOF" >> "$GITHUB_OUTPUT"

  - name: Use multi-line output
    run: echo "${{ steps.changelog.outputs.content }}"


Step Outcome and Conclusion

Every step has an outcome you can check:


steps:
  - name: Risky step
    id: risky
    continue-on-error: true
    run: exit 1

  - name: Check outcome
    run: |
      echo "Outcome: ${{ steps.risky.outcome }}"         # failure
      echo "Conclusion: ${{ steps.risky.conclusion }}"    # success (because continue-on-error)

Property With continue-on-error: true Without
outcome failure (actual result) failure
conclusion success (after applying continue-on-error) failure

Common Mistakes

Mistake Fix
Using deprecated ::set-output Use echo "key=value" >> "$GITHUB_OUTPUT"
Using deprecated ::set-env Use echo "KEY=value" >> "$GITHUB_ENV"
Missing step id: Outputs require an id: on the producing step
Env var not available $GITHUB_ENV only affects subsequent steps, not the current one
Multi-line value truncated Use the delimiter syntax with <<EOF

Quick Reference

File Purpose Access
$GITHUB_OUTPUT Step outputs ${{ steps.id.outputs.key }}
$GITHUB_ENV Persistent env vars $KEY or ${{ env.KEY }}
$GITHUB_PATH Add to PATH Available in next steps
$GITHUB_STEP_SUMMARY Job summary markdown Renders in Actions UI

← Previous: Action Inputs & Outputs

Next: Practice Exercises →

Updated: