Defining and Calling GitHub Actions Reusable Workflows: A Practical Guide
Learn how to define a GitHub Actions reusable workflow, call it with inputs and secrets, and avoid common pitfalls such as missing triggers, wrong version references, and double checkout.
06 Jan 2026, 10:14 UTC

Useful answer
To avoid duplicating CI logic, create a reusable workflow that defines jobs, steps, inputs, secrets, and outputs, then call it from other workflows using uses: with the appropriate with: and secrets: blocks. The reusable workflow must reside in .github/workflows of the source repository and be triggered only by workflow_call.
Worked example
Reusable workflow definition
File: .github/workflows/node-build.yml in the repository that will be reused.
name: Node Build (reusable)
on:
workflow_call:
inputs:
node-version:
description: 'Version of Node.js to use'
required: true
type: string
secrets:
NPM_TOKEN:
description: 'Token for publishing to npm'
required: true
outputs:
artifact-url:
description: 'URL of the built artifact'
value: ${{ jobs.build.outputs.artifact_url }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact_url: ${{ steps.publish.outputs.artifact_url }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build package
run: npm run build
- name: Publish to npm (optional)
id: publish
if: secrets.NPM_TOKEN != ''
run: |
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
npm publish
echo "artifact_url=https://www.npmjs.com/package/$(jq -r .name package.json)" >> $GITHUB_OUTPUT
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
This workflow defines an input node-version, a required secret NPM_TOKEN, and an output artifact-url that reports where the built package was published (if the secret is present).
Caller workflow
File: .github/workflows/release.yml in any repository that wants to reuse the logic.
name: Release pipeline
on:
push:
branches: [ main ]
jobs:
build-and-release:
uses: ./.github/workflows/node-build.yml@v1 # reference a tag, branch, or commit
with:
node-version: '20.x'
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
The caller passes the Node version and the npm token. When the workflow runs, GitHub loads the reusable workflow from the same repository (note the relative path ./.github/workflows/node-build.yml) and executes its build job. The output artifact-url can be accessed later in the caller via ${{ jobs.build-and-release.outputs.artifact-url }} if needed.
Limits and constraints
- Trigger restriction: A reusable workflow can only have
on: workflow_call. It cannot definepush,pull_request,schedule, or other event triggers. - Location requirement: The file must be inside
.github/workflowsof the source repository; otherwise the call fails with “workflow not found”. - Reference specificity: The
uses:path must point to an existing ref (branch, tag, or commit). Using a moving target like@mainis allowed but can introduce unexpected changes; pinning to a tag or commit SHA is safer for reproducibility. - Cross‑repo calling: You can call a reusable workflow from another repository only if:
- the target repository is public,
- or it belongs to the same organization or enterprise as the caller,
- or the caller has been granted explicit permission via enterprise policies.
- Permission inheritance: The reusable workflow runs with the permissions of the caller only if the caller explicitly grants them via
permissions:in theuses:block. By default, it receives the minimal set of permissions needed for theGITHUB_TOKEN. - Context inheritance: Certain GitHub contexts (e.g.,
github.event_name,github.ref) reflect the caller’s event, not the reusable workflow’s definition.
Common mistakes and how to avoid them
1. Missing workflow_call trigger
If you forget to add on: workflow_call, GitHub treats the file as a regular workflow and will not allow it to be called. The caller will fail with “workflow not found” or “invalid workflow”.
2. Referencing the wrong version
Using uses: ./.github/workflows/node-build.yml without a ref defaults to the repository’s default branch, which may change. If you intend to lock a version, tag the file (e.g., v1) and reference uses: ./.github/workflows/node-build.yml@v1. Verify the tag exists before pushing the caller.
3. Forgetting to pass required secrets
The reusable workflow declares secrets.NPM_TOKEN as required. If the caller omits the secrets: block or provides an empty value, the job will fail when it tries to use the secret. Double‑check that the secret name matches exactly and that it is defined in the caller’s repository or organization secrets.
4. Double checkout when using actions/checkout inside the reusable workflow
If the caller already checks out the repository (a common pattern) and the reusable workflow also runs actions/checkout, you may end up with two checkouts, wasting minutes and potentially causing cache conflicts. Either let the caller handle checkout and remove it from the reusable workflow, or document that the reusable workflow expects a clean workspace and avoid checkout in the caller.
5. Assuming output availability before the job finishes
Outputs from a reusable workflow are only accessible after the job completes. Trying to reference ${{ jobs.job-id.outputs.foo }} in a later job of the caller before the reusable job finishes will result in an empty value. Ensure the dependent job lists the reusable job as a needs: dependency.
Practical verification steps
- Create a test repository (or use an existing one where you have write access).
- Add the reusable workflow file
.github/workflows/node-build.ymlwith the content shown above. - Add a caller workflow file
.github/workflows/release.ymlthat calls the reusable workflow with inputs and secrets. - Create a repository secret named
NPM_TOKEN(you can use a dummy token for testing). - Commit and push the changes to the default branch.
- Go to the Actions tab; you should see a run of the Release pipeline workflow.
- Open the run, expand the Build and release job, and check the logs for the steps from the reusable workflow (e.g.,
Setup Node.js,Run tests). - If the
NPM_TOKENsecret is present and valid, look for a line similar toartifact_url=https://www.npmjs.com/package/<package-name>in thePublish to npmstep output. - To verify propagation, update the reusable workflow (e.g., change the Node version input default or modify the build step), commit a new tag
v2, and update the caller to useuses: ./.github/workflows/node-build.yml@v2. Trigger the caller again and confirm the new behavior appears in the logs. - Optionally, fork the repository or create a second repository within the same organization and add a caller workflow that references the reusable workflow using
uses: owner/repo/.github/workflows/node-build.yml@v1. Ensure the call succeeds, confirming cross‑repo access works under the organization’s permissions.
Risks to consider
- Exposing secrets: Ensure any secret passed to a reusable workflow is truly required and limit its scope using repository‑level secret permissions.
- Version drift: Using a branch ref like
@maincan cause sudden breaking changes; prefer tags or commit SHAs for production pipelines. - Permission gaps: If the reusable workflow needs additional permissions (e.g., to create a release), explicitly add
permissions:in the caller’suses:block.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.