Skip to content
Latchkey

How to Use a Path-Filtered Matrix in CI

A static matrix builds every component on every change. Filtering the matrix by changed paths runs only the parts a diff actually touched.

A path-filtered matrix is generated at runtime from the changed files in a PR. Instead of building all services, the matrix contains only the ones whose paths changed, so CI scales with the diff.

1. Detect which paths changed

Use dorny/paths-filter to classify the diff into named filters.

.github/workflows/ci.yml
- uses: dorny/paths-filter@v3
  id: filter
  with:
    filters: |
      api: 'services/api/**'
      web: 'services/web/**'
      worker: 'services/worker/**'

2. Build the matrix from the filters

Emit a JSON list of changed components and feed it into a downstream job matrix.

.github/workflows/ci.yml
- id: set
  run: |
    MATRIX=$(jq -nc --argjson api ${{ steps.filter.outputs.api }} \
      '[ if $api then "api" else empty end ]')
    echo "matrix=$MATRIX" >> $GITHUB_OUTPUT

3. Handle the empty matrix

If nothing relevant changed the matrix is empty and the job is skipped. Pair with a required-check shim so an empty matrix does not block merges.

4. Combine with shared dependency caches

Each matrix leg still installs dependencies. A warm dependency cache, or a Latchkey runner that keeps deps hot on the image, keeps each leg fast even as the matrix grows.

Key takeaways

  • Generate the matrix from changed paths so it scales with the diff.
  • Use paths-filter to classify the diff into named components.
  • Handle the empty-matrix case so required checks still pass.

Related guides

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