Skip CI When Only Documentation Changed
Use a paths filter or dorny/paths-filter to skip GitHub Actions CI runs when a PR only touches Markdown or docs files.
A GitHub Actions pattern that skips a CI workflow entirely when a push only touches documentation files. The simplest version uses GitHub's built-in paths-ignore filter at the workflow level. The more flexible version uses dorny/paths-filter to gate individual jobs. Both can save serious CI minutes on docs-heavy projects.
Tested on GitHub Actions ubuntu-latest, dorny/paths-filter@v3.
When to Use This
- Docs-heavy projects where Markdown and code mix in the same repo
- Projects with frequent README and CHANGELOG updates
- Monorepos where docs changes shouldn't rebuild the whole world
- Any repo where CI minutes are a real cost
Don't use this when your docs are part of the build (e.g., a docs site that gets built and deployed) — those need CI too. And don't skip security scans even for docs PRs.
Code
The simple version uses paths-ignore at the workflow level:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
pull_request:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- 'LICENSE'
- '.gitignore'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# ... real CI work ...If a push or PR only touches Markdown files, the workflow doesn't even start.
The more flexible version uses dorny/paths-filter inside a job:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
code:
- 'src/**'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
test:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm testThe changes job is fast (10-15 seconds — just a checkout and a diff). The test job only runs if real source code changed.
Usage
A real production setup with multiple gated jobs:
jobs:
changes:
runs-on: ubuntu-latest
outputs:
code: ${{ steps.filter.outputs.code }}
docs: ${{ steps.filter.outputs.docs }}
ci: ${{ steps.filter.outputs.ci }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
code:
- 'src/**'
- 'package.json'
- 'package-lock.json'
docs:
- 'docs/**'
- '**.md'
ci:
- '.github/**'
test:
needs: changes
if: needs.changes.outputs.code == 'true' || needs.changes.outputs.ci == 'true'
runs-on: ubuntu-latest
steps:
- run: npm ci && npm test
link-check:
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
steps:
- run: npm run check-links
workflow-lint:
needs: changes
if: needs.changes.outputs.ci == 'true'
runs-on: ubuntu-latest
steps:
- uses: rhysd/actionlint@v1Tests only run on code or CI changes. Link checking only runs on docs changes. Workflow linting only runs on CI changes. Each PR runs only what's relevant.
Pitfalls
paths-ignoreandpathsare mutually exclusive at the workflow level. Pick one. Usepaths-ignorefor "skip these",pathsfor "only these".paths-ignoreonly filters by changed files, not by file content. A whitespace-only edit to a.tsfile still triggers CI. That's correct behavior.- Required status checks break with skipped workflows. If
testis a required check and the workflow is skipped, GitHub thinks the check is missing, not passing. Use a separate "always run" workflow that returns success for skipped cases, or mark the check as "skip-when-not-required". dorny/paths-filterneeds the rightbasefor PRs. It defaults to comparing against the PR target branch, which is usually what you want.- PR checks count even when skipped. GitHub still counts the workflow as having run, just with no jobs. That's fine.
- Don't use this for security workflows. Code scanning, dependency audits, and license checks should run on every PR regardless of which files changed.
Related Snippets & Reading
- Next.js CI Workflow — wrap with this for docs-aware CI
- Cache npm Dependencies — another CI optimization
- dorny/paths-filter — official action docs
Frequently Asked Questions
Why skip CI for docs changes?
CI minutes are billable on private repos and slow on every plan. A README typo doesn't need a 5-minute build, lint, and test cycle. Skipping docs-only changes can save hours of CI minutes per month on docs-heavy projects without weakening code quality gates.
What's the difference between paths and dorny/paths-filter?
The on.push.paths filter is built into GitHub Actions and skips the entire workflow if no matching files changed. dorny/paths-filter runs inside a job and lets you conditionally skip individual steps based on which files changed. Use paths for whole workflows, dorny for finer control.