GitHub Actions Reusable Workflows and Custom (Composite) Actions

Copy-pasted YAML is hard to maintain. In this lesson you will create reusable workflows that other pipelines call as jobs, build a composite action for shared setup steps, and learn how to use third-party actions safely.

Advertisement

📖 What You’ll Learn: reusable workflows vs composite actions, defining workflow_call with inputs, secrets and outputs, calling workflows from the same and other repositories, composite actions and their rules, JavaScript and Docker actions, and safe use of third-party actions.


♻️ Why Reuse?

As repositories multiply, the same YAML gets copied everywhere: checkout, set up Node, install, run tests, upload the report. Copies drift apart and every fix must be made ten times. GitHub Actions has two ways to reuse:

Reusable workflowComposite action
ReusesWhole jobs (one or more)Steps inside a job
Defined withon: workflow_call in .github/workflows/action.yml with runs.using: composite
Called withjobs.<id>.uses:steps[*].uses:
Can choose its own runner✅❌ (runs in the caller’s job)
Can use secrets directlyPassed in by the callerVia inputs only
Best forStandard pipelines (test, deploy)Small shared step sequences (setup, notify)

📄 Reusable Workflows

1. Define the reusable workflow

# .github/workflows/playwright-tests.yml
name: Playwright tests (reusable)

on:
  workflow_call:
    inputs:
      project:
        type: string
        default: chromium
      base-url:
        type: string
        required: true
    secrets:
      TEST_PASSWORD:
        required: true
    outputs:
      result:
        description: Test result
        value: ${{ jobs.e2e.outputs.result }}

jobs:
  e2e:
    runs-on: ubuntu-latest
    outputs:
      result: ${{ steps.run.outcome }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps ${{ inputs.project }}
      - id: run
        run: npx playwright test --project=${{ inputs.project }}
        env:
          BASE_URL: ${{ inputs.base-url }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

2. Call it

# .github/workflows/ci.yml
name: CI
on: [ pull_request ]

jobs:
  chromium:
    uses: ./.github/workflows/playwright-tests.yml          # same repository
    with:
      project: chromium
      base-url: https://staging.example.com
    secrets:
      TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

  firefox:
    uses: acme/ci-templates/.github/workflows/playwright-tests.yml@v1   # another repository
    with:
      project: firefox
      base-url: https://staging.example.com
    secrets: inherit                                         # pass all of the caller's secrets
RuleDetail
Reference./.github/workflows/file.yml (same repo) or owner/repo/.github/workflows/file.yml@ref
VersionPin @ref to a tag (@v1) or commit SHA for stability
InputsTyped: string, number, boolean
SecretsPassed explicitly or with secrets: inherit
AccessThe called workflow must be in a public repository, or in a private repository whose settings allow access
MatrixYou can call a reusable workflow inside a matrix with strategy on the calling job

🧱 Composite Actions

A composite action bundles steps. Create a folder with an action.yml:

# .github/actions/setup-playwright/action.yml
name: Setup Playwright
description: Install Node, dependencies and Playwright browsers
inputs:
  browsers:
    description: Browsers to install
    default: chromium
runs:
  using: composite
  steps:
    - uses: actions/setup-node@v4
      with:
        node-version: lts/*
        cache: npm
    - run: npm ci
      shell: bash
    - run: npx playwright install --with-deps ${{ inputs.browsers }}
      shell: bash

📝 Note: Every run step in a composite action must declare a shell.

Use it in any job:

steps:
  - uses: actions/checkout@v4                       # checkout first: local actions live in the repo
  - uses: ./.github/actions/setup-playwright
    with:
      browsers: chromium firefox
  - run: npx playwright test

🧰 Other Kinds of Custom Actions

TypeWritten inUse when
CompositeYAML stepsCombining existing commands and actions
JavaScriptNode.js (runs.using: node20 or newer)Logic, API calls, fast startup
Docker containerAny language in a containerSpecific tools or environments (Linux runners only)

Publish actions from a public repository to the GitHub Marketplace by adding an action.yml in the root and creating a release.


🛡️ Using Third-Party Actions Safely

  • Prefer actions from verified creators (GitHub, Docker, Microsoft, AWS…).
  • Pin third-party actions to a full commit SHA (with a version comment) so a moved tag cannot change what runs: uses: some-org/some-action@<40-char-sha> # v2.1.0.
  • Let Dependabot update action versions with package-ecosystem: github-actions.
  • Limit permissions of GITHUB_TOKEN (secrets and environments).

📝 Assignment

  1. Create a reusable workflow test.yml with a project input and a required secret, and call it twice from ci.yml for two browsers.
  2. Return an output from the reusable workflow and print it in the caller.
  3. Create a composite action setup-playwright and use it in a job.
  4. Call a reusable workflow inside a matrix.
  5. Pin one third-party action to a commit SHA and add a Dependabot config for GitHub Actions updates.

❓ FAQ

🤔 What is a reusable workflow in GitHub Actions?

A workflow with the workflow_call trigger that other workflows can call as a job, passing inputs and secrets.

🤔 What is the difference between a reusable workflow and a composite action?

A reusable workflow reuses whole jobs and chooses its own runner. A composite action reuses a sequence of steps inside the caller’s job.

🤔 How do I pass secrets to a reusable workflow?

Under secrets: in the calling job, either one by one or with secrets: inherit.

🤔 How do I create a custom GitHub Action?

Create an action.yml describing inputs and how it runs (composite, JavaScript or Docker), in its own folder or repository, then reference it with uses:.

🤔 Should I pin GitHub Actions to a version?

Yes. Use a version tag for trusted publishers and a full commit SHA for third-party actions to avoid unexpected changes.


🎯 Next Steps

🎬 Learn Git and GitHub step by step: watch the series on YouTube!

Subscribe to SqaToolsByDY • Practice on sqatools.in

Leave a Comment