GitHub Actions Tutorial — Step Outputs
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_OUTPUTfor data you’ll use inif:conditions or pass to other jobs - Use
$GITHUB_ENVfor 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 →