When you have a lot of workflows in your repository. There would be a scenario where you want to reuse some steps instead of writing them repeatedly and updating them when the versions are updated in multiple files.

Creating a composite action is one way to achieve this with GitHub Actions.

One Common Example I can think of is Setting up the desired language and installing the dependencies. Which is most common and used in most of the workflows.

To create a re-usable action that can be used in your workflows. you have to create an action.yml file under the .github folder

Let’s call our action setup-node-bun-install

  1. Let’s create a file in this way .github/actions/setup-node-bun-install/action.yml
runs:
  using: "composite"
  steps:

One thing to observe in this is the runs property that contains the list of steps to run

The Folder structure for this would be something like this

.github
  - workflows
    - lint-ts.yml
  - actions
    - setup-node-bun-install/action.yml
    - eas-build/action.yml
    - setup-jdk-generate-apk.yml

Let’s implement the setup-node-bun-install composite action

name: "Setup Node + Bun + Install Dependencies"
description: "Setup Node.js, Bun, and install project dependencies."
runs:
  using: "composite"
  steps:
    - name: 📦 Setup Node.js
      uses: actions/setup-node@v4
 
    - uses: oven-sh/setup-bun@v2
      with:
        bun-version: latest
 
    - name: 📦 Install Project Dependencies
      run: bun install --frozen-lockfile
      shell: bash

Now this composite action can be used like this

name: Type Check (tsc)
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  type-check:
    name: Type Check (tsc)
    runs-on: ubuntu-latest
    steps:
      - name: 📦 Checkout project repo
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - name: 📦 Setup Node + Bun + Install Dependencies
        uses: ./.github/actions/setup-node-bun-install
 
      - name: 📦 Install Reviewdog
        if: github.event_name == 'pull_request'
        uses: reviewdog/action-setup@v1
 
      - name: 🏃‍♂️ Run TypeScript PR # Reviewdog tsc errorformat: %f:%l:%c - error TS%n: %m
        # We only need to add the reviewdog step if it's a pull request
        if: github.event_name == 'pull_request'
        run: |
          bun type-check | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee
        env:
          REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      
      - name: 
          🏃‍♂️ Run TypeScript Commit
          # If it's not a Pull Request then we just need to run the type-check
        if: github.event_name != 'pull_request'
        run: bun type-check