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.
- 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.
- id: set
run: |
MATRIX=$(jq -nc --argjson api ${{ steps.filter.outputs.api }} \
'[ if $api then "api" else empty end ]')
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT3. 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.