GitHub Actions Tutorial — Branch & Path Filtering
Branch & Path Filtering
Not every push or pull request needs to trigger your entire CI pipeline. Branch and path filters let you control exactly which changes trigger which workflows — saving runner minutes and reducing noise.
Branch Filtering
Include Specific Branches
on:
push:
branches:
- main
- develop
- 'release/**'
This triggers only on pushes to main, develop, or any branch matching release/** (e.g., release/v2.0, release/hotfix).
Exclude Branches
on:
push:
branches-ignore:
- 'docs/**'
- 'dependabot/**'
This triggers on pushes to all branches except those matching docs/** or dependabot/**.
Rule: You can use branches or branches-ignore, but not both in the same trigger.
Tag Filtering
Trigger on Tags
on:
push:
tags:
- 'v*'
This triggers when a tag matching v* is pushed (e.g., v1.0.0, v2.3.1-beta).
Exclude Tags
on:
push:
tags-ignore:
- 'v*-rc*'
This triggers on all tags except release candidates.
Path Filtering
Include Specific Paths
on:
push:
paths:
- 'src/**'
- 'package.json'
- 'package-lock.json'
The workflow triggers only when changed files match at least one of these patterns. If you only change README.md, the workflow won’t run.
Exclude Paths
on:
push:
paths-ignore:
- 'docs/**'
- '**.md'
- '.gitignore'
This triggers on pushes unless the only changes are to documentation files, markdown files, or .gitignore.
Rule: You can use paths or paths-ignore, but not both in the same trigger.
Combining Filters
You can combine branch and path filters:
on:
push:
branches: [main]
paths:
- 'src/**'
- 'tests/**'
This triggers only when:
- The push is to
mainAND - At least one changed file matches
src/**ortests/**
Both conditions must be true.
Glob Patterns
GitHub Actions uses these glob patterns for filtering:
| Pattern | Matches |
|---|---|
* |
Any characters except /
|
** |
Any characters including / (recursive) |
? |
Any single character |
[abc] |
Any character in the set |
[a-z] |
Any character in the range |
! |
Negate a pattern (when prefixed) |
Examples:
| Pattern | Matches |
|---|---|
src/** |
src/app.js, src/lib/utils.js
|
*.js |
app.js (root only, not src/app.js) |
**/*.js |
Any .js file at any depth |
release/v[0-9]* |
release/v1, release/v2.0
|
Real-World Examples
Frontend CI — Only on Frontend Changes
name: Frontend CI
on:
push:
branches: [main]
paths:
- 'frontend/**'
- 'shared/**'
pull_request:
branches: [main]
paths:
- 'frontend/**'
- 'shared/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd frontend && npm test
Backend CI — Ignore Documentation
name: Backend CI
on:
push:
branches: [main, develop]
paths-ignore:
- 'docs/**'
- '**.md'
- '.github/ISSUE_TEMPLATE/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./gradlew test
Release Pipeline — Tags Only
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # matches v1.0.0, v2.3.1
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Publishing version ${{ github.ref_name }}"
Common Mistakes
| Mistake | Fix |
|---|---|
Using branches and branches-ignore together |
Pick one — they’re mutually exclusive |
Using paths and paths-ignore together |
Pick one — they’re mutually exclusive |
| Path filter never matches | Check glob patterns; *.js only matches root, use **/*.js for recursive |
| Workflow still runs on docs changes | Ensure paths-ignore covers all doc patterns including **.md
|
Quick Reference
| Filter | Purpose | Example |
|---|---|---|
branches: |
Include branches | [main, develop] |
branches-ignore: |
Exclude branches | ['docs/**'] |
tags: |
Include tags | ['v*'] |
tags-ignore: |
Exclude tags | ['v*-rc*'] |
paths: |
Include file paths | ['src/**'] |
paths-ignore: |
Exclude file paths | ['**.md'] |
← Previous: Push & Pull Request
Next: Schedule & Dispatch →