Skip to content
Latchkey

How to Set Up CI for a Monorepo With Changed-Path Filters in GitHub Actions

dorny/paths-filter detects which packages changed in a PR so you only build and test what was touched.

A detect job runs dorny/paths-filter to set per-package outputs, then each package job declares needs: detect with an if that checks its filter output. Unchanged packages are skipped, saving minutes.

Steps

  • Add a detect job that runs dorny/paths-filter with one filter per package.
  • Expose the filter results as job outputs.
  • Gate each package job with if: needs.detect.outputs.<pkg> == 'true'.
  • Install, lint, and test inside each package job.

Workflow

.github/workflows/ci.yml
name: CI
on:
  pull_request:
jobs:
  detect:
    runs-on: ubuntu-latest
    outputs:
      api: ${{ steps.filter.outputs.api }}
      web: ${{ steps.filter.outputs.web }}
    steps:
      - uses: actions/checkout@v4
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            api:
              - 'packages/api/**'
            web:
              - 'packages/web/**'
  api:
    needs: detect
    if: needs.detect.outputs.api == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci --workspace packages/api
      - run: npm run lint --workspace packages/api
      - run: npm test --workspace packages/api

Gotchas

  • A skipped required check can block merges; add an aggregate job that succeeds when all needed jobs pass or skip.
  • On push to a base branch, paths-filter compares against the previous commit, not a PR base.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →