GitHub Actions Tutorial — Inputs, Outputs, Secrets

2 minute read

Inputs, Outputs, Secrets

Reusable workflows accept inputs (parameters), return outputs (results), and can receive secrets — just like function arguments and return values.


Inputs

Define inputs in the reusable workflow under workflow_call:


# .github/workflows/deploy.yml (reusable)
on:
  workflow_call:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: string
      version:
        description: 'Version to deploy'
        required: true
        type: string
      dry-run:
        description: 'Skip actual deployment'
        required: false
        type: boolean
        default: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Deploying v${{ inputs.version }} to ${{ inputs.environment }}"
          echo "Dry run: ${{ inputs.dry-run }}"

Input Types

Type Example Notes
string 'production' Default type
boolean true / false Must be true or false
number 42 Passed as a string, parsed as number

Caller Passing Inputs


jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
      version: '2.1.0'
      dry-run: false


Secrets

Reusable workflows don’t automatically inherit the caller’s secrets. You must declare them:


# .github/workflows/deploy.yml (reusable)
on:
  workflow_call:
    inputs:
      environment:
        type: string
        required: true
    secrets:
      AWS_ACCESS_KEY_ID:
        required: true
      AWS_SECRET_ACCESS_KEY:
        required: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    steps:
      - run: echo "Deploying with AWS credentials..."

Caller Passing Secrets


jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

inherit — Pass All Secrets

Instead of listing each secret, use secrets: inherit:


jobs:
  deploy:
    uses: ./.github/workflows/deploy.yml
    with:
      environment: production
    secrets: inherit                # pass ALL caller secrets

secrets: inherit forwards every secret the caller has access to. Simple but less explicit.


Outputs

Reusable workflows can return values via outputs:


# .github/workflows/build.yml (reusable)
on:
  workflow_call:
    outputs:
      version:
        description: 'Built version'
        value: ${{ jobs.build.outputs.version }}
      artifact-name:
        description: 'Artifact name'
        value: ${{ jobs.build.outputs.artifact-name }}

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.ver.outputs.version }}
      artifact-name: ${{ steps.ver.outputs.artifact-name }}
    steps:
      - id: ver
        run: |
          VERSION="2.1.0-$(date +%s)"
          echo "version=$VERSION" >> "$GITHUB_OUTPUT"
          echo "artifact-name=build-$VERSION" >> "$GITHUB_OUTPUT"

      - uses: actions/upload-artifact@v4
        with:
          name: build-${{ steps.ver.outputs.version }}
          path: ./dist

Caller Reading Outputs


jobs:
  build:
    uses: ./.github/workflows/build.yml

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: |
          echo "Version: ${{ needs.build.outputs.version }}"
          echo "Artifact: ${{ needs.build.outputs.artifact-name }}"


Complete Example


# Reusable: .github/workflows/test-and-build.yml
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: '20'
    secrets:
      NPM_TOKEN:
        required: false
    outputs:
      passed:
        description: 'Whether tests passed'
        value: ${{ jobs.test.outputs.passed }}

jobs:
  test:
    runs-on: ubuntu-latest
    outputs:
      passed: ${{ steps.result.outputs.passed }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
      - run: npm test
      - id: result
        if: success()
        run: echo "passed=true" >> "$GITHUB_OUTPUT"


# Caller: .github/workflows/ci.yml
on:
  push:
    branches: [main]

jobs:
  test:
    uses: ./.github/workflows/test-and-build.yml
    with:
      node-version: '20'
    secrets: inherit

  deploy:
    needs: test
    if: needs.test.outputs.passed == 'true'
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying — tests passed!"


Common Mistakes

Mistake Fix
Secret not available in reusable workflow Declare it under secrets: or use secrets: inherit
Output is empty Chain: step output → job output → workflow output
Input type mismatch Boolean inputs must be true/false, not strings
Using env context for inputs Use inputs.name, not env.name

Quick Reference

Feature Reusable Workflow Caller
Inputs workflow_call.inputs.<name> with: <name>: value
Secrets workflow_call.secrets.<name> secrets: <name>: value
All secrets — secrets: inherit
Outputs workflow_call.outputs.<name>.value needs.<job>.outputs.<name>

← Previous: workflow_call

Next: Calling Workflows →

Updated: