Blog/Quick Tips & Snippets/GitHub Actions Reusable Workflows: Stop Repeating Yourself
POST
January 22, 2026
LAST UPDATEDJanuary 22, 2026

GitHub Actions Reusable Workflows: Stop Repeating Yourself

Create reusable GitHub Actions workflows with inputs, secrets, and outputs to eliminate YAML duplication across repositories and teams efficiently.

Tags

GitHub ActionsCI/CDDevOpsAutomation
GitHub Actions Reusable Workflows: Stop Repeating Yourself
3 min read

GitHub Actions Reusable Workflows: Stop Repeating Yourself

TL;DR

Reusable workflows let you define CI/CD pipelines once and call them from multiple repositories using workflow_call. Stop copying YAML files between repos and start treating your pipelines like shared functions.

The Problem

Every new repository gets the same CI/CD boilerplate — lint, test, build, deploy. You copy the workflow YAML, tweak a few values, and move on. Six months later, you need to update the Node.js version across all pipelines. Now you are editing the same YAML in twelve repositories and hoping you do not miss one.

Composite actions help with individual steps, but they cannot define entire jobs or control job ordering. You need something that encapsulates a complete workflow — jobs, steps, conditions, and all — and exposes it as a callable unit.

The Solution

Create a reusable workflow with the workflow_call trigger. This turns a workflow file into a function that other workflows can invoke with inputs and secrets.

Step 1: Define the reusable workflow.

yaml
# .github/workflows/ci-pipeline.yml (in your shared repo)
name: CI Pipeline
 
on:
  workflow_call:
    inputs:
      node-version:
        description: 'Node.js version to use'
        required: false
        type: string
        default: '20'
      working-directory:
        description: 'Directory containing package.json'
        required: false
        type: string
        default: '.'
    secrets:
      NPM_TOKEN:
        required: false
 
jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
      - run: npm run lint
      - run: npm test
 
  build:
    needs: lint-and-test
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build

Step 2: Call it from any repository.

yaml
# .github/workflows/ci.yml (in your project repo)
name: CI
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  ci:
    uses: your-org/shared-workflows/.github/workflows/ci-pipeline.yml@main
    with:
      node-version: '20'
    secrets: inherit  # Pass all secrets from the caller

Passing secrets explicitly when you need fine-grained control:

yaml
jobs:
  ci:
    uses: your-org/shared-workflows/.github/workflows/ci-pipeline.yml@main
    secrets:
      NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Composite actions vs. reusable workflows: Composite actions bundle multiple steps into a single reusable step. Reusable workflows encapsulate entire jobs with their own runners and job dependencies. Use composite actions for shared step sequences (like "setup Node + install deps"). Use reusable workflows for shared pipelines (like "lint + test + build + deploy").

Why This Works

Reusable workflows centralize pipeline logic. When you update the shared workflow — add a security scan step, bump a dependency version, change the test command — every repository that calls it picks up the change automatically. Pin to a specific tag (@v1) for stability, or reference @main for always-latest behavior.

The secrets: inherit shorthand passes all caller secrets without listing them individually, which keeps the calling workflow minimal. For repositories that need different behavior, inputs provide the escape hatch without forking the entire workflow.

FAQ

What is a reusable workflow in GitHub Actions?

It is a workflow that uses the workflow_call trigger, allowing other workflows to call it like a function with inputs, secrets, and outputs.

How do I pass secrets to a reusable workflow?

Define secrets in the reusable workflow's on.workflow_call.secrets section, then pass them from the caller workflow using the secrets key or secrets: inherit.

Can reusable workflows be in a different repository?

Yes, reference them with the full path like owner/repo/.github/workflows/reusable.yml@main, and the repository must be public or in the same organization.

Collaboration

Need help with a project?

Let's Build It

I help startups and established companies design, build, and scale world-class digital products. From deep technical architecture to pixel-perfect UI — let's bring your vision to life.

SH

Article Author

Sadam Hussain

Senior Full Stack Developer

Senior Full Stack Developer with over 7 years of experience building React, Next.js, Node.js, TypeScript, and AI-powered web platforms.

Related Articles

TypeScript Utility Types You Should Know
Feb 10, 20263 min read
TypeScript
Cheatsheet

TypeScript Utility Types You Should Know

Five essential built-in generic utility types in TypeScript that will save you hundreds of lines of code.

Generate Dynamic OG Images in Next.js
Feb 08, 20262 min read
Next.js
OG Images
SEO

Generate Dynamic OG Images in Next.js

Generate dynamic Open Graph images in Next.js using the ImageResponse API with custom fonts, gradients, and data-driven content for social sharing.

Node.js Streams for Processing Large Files
Jan 05, 20262 min read
Node.js
Streams
Performance

Node.js Streams for Processing Large Files

Process large files efficiently in Node.js using readable, writable, and transform streams to avoid memory issues and handle data chunk by chunk.