CODEOWNERS File
A CODEOWNERS file maps file paths to the people or teams responsible for them. When a PR touches a path, GitHub automatically requests review from the matching owners. Combine it with branch protection requiring code-owner review, and ownership becomes enforceable instead of polite. It’s the single highest-leverage governance file in a multi-team repo.
Where the file lives
GitHub looks for CODEOWNERS in three locations (any one works; pick one):
.github/CODEOWNERS← most commonCODEOWNERSat the repository rootdocs/CODEOWNERS
If multiple exist, the first one found wins (in the order above).
Basic syntax
.github/CODEOWNERS
# Each line: path-pattern one-or-more-owners # Owners can be users (@alice), teams (@acme-co/frontend), or emails. # Lines starting with # are comments. # Global fallback — every file has these owners unless overridden below * @alice @bob # Frontend /src/components/ @acme-co/frontend /src/pages/ @acme-co/frontend *.css @acme-co/design # Backend /server/ @acme-co/backend /migrations/ @acme-co/backend @acme-co/dbas # Infra /.github/workflows/ @acme-co/devops /terraform/ @acme-co/devops # Docs /docs/ @charlie *.md @charlie # A specific file /SECURITY.md @acme-co/security
Three kinds of owners
Users —
@alice,@bob. Must have write access to the repo.Teams —
@acme-co/frontend. Team must have write access; team members are notified.Email addresses —
alice@example.com. Useful for non-GitHub recipients; doesn’t request review on GitHub.
Path patterns
Patterns look like .gitignore but with a few important differences:
*matches one path segment;**matches any number./foo/matches thefoodirectory and everything inside.A leading
/anchors to the repo root; without it, the pattern matches anywhere.*.jsmatches.jsfiles at any depth.Character classes (
[abc]) work, but?does not match a single character (unlike gitignore).You can’t negate (
!pattern) — unlike gitignore.
Pattern examples
* # everything (fallback) *.js # any .js file, any depth docs/ # the top-level docs dir /docs/ # same — anchored anyway docs/* # files directly in docs/, not subdirs docs/** # everything under docs/, recursive src/api/*.ts # .ts files directly in src/api/ src/**/__tests__/ # any __tests__/ dir under src/
Order matters — last match wins
Correct ordering: general → specific
# Right * @alice /src/payments/ @acme-co/payments # Wrong — the generic * comes last and clobbers the specific rule /src/payments/ @acme-co/payments * @alice
Required reviewers via branch protection
CODEOWNERS by itself only requests review — it doesn’t enforce. To require it:
Repo → Settings → Branches → Branch protection rules → Add rule.
Apply to
main(and any other protected branches).Tick Require a pull request before merging.
Tick Require review from Code Owners.
Now any PR touching an owned path needs an approval from at least one owner before the merge button activates.
Example: a realistic monorepo
A working CODEOWNERS for a multi-team monorepo
# ───────── global fallback ───────── * @acme-co/maintainers # ───────── apps ───────── /apps/web/ @acme-co/frontend /apps/web/src/billing/ @acme-co/frontend @acme-co/billing /apps/admin/ @acme-co/internal-tools /apps/mobile/ @acme-co/mobile # ───────── shared packages ───────── /packages/design-system/ @acme-co/design @acme-co/frontend /packages/auth/ @acme-co/identity /packages/db/ @acme-co/data-platform # ───────── infra ───────── /.github/workflows/ @acme-co/devops /terraform/ @acme-co/devops /k8s/ @acme-co/devops @acme-co/sre # ───────── high-risk paths ───────── /security/ @acme-co/security /SECURITY.md @acme-co/security /.github/CODEOWNERS @acme-co/maintainers # ───────── docs ───────── *.md @acme-co/docs /docs/api/ @acme-co/docs @acme-co/backend
Validating the file
GitHub renders a syntax check at the top of the file view if you’ve made errors. You can also validate with the CLI:
View parsed CODEOWNERS rules and validation errors
gh api repos/acme-co/widget/codeowners/errors --jq '.errors' # Or open in the browser: # https://github.com/acme-co/widget/blob/main/.github/CODEOWNERS # GitHub shows a banner with any parse errors.
Common pitfalls
Owner without write access — the rule is silently ignored. CODEOWNERS rules require write permission.
Misspelled team name — also silently ignored. Test with a real PR.
Wrong rule order — a generic catch-all at the bottom overrides specific rules.
Trailing-slash gotcha —
docsmatches files nameddocs;docs/matches the directory. Always include the slash for directory patterns.Pattern doesn’t match what you think — there’s no
!negation; build whitelist patterns instead.Stale ownership — people leave teams; review CODEOWNERS quarterly.
Real-world workflow
Roll out CODEOWNERS on an existing repo
# 1. Draft the file mkdir -p .github $EDITOR .github/CODEOWNERS # 2. Commit and open a PR — let the affected teams review git add .github/CODEOWNERS git commit -m "Add CODEOWNERS for path-based review" git push -u origin add-codeowners gh pr create --fill # 3. After merge, enable branch protection to require code-owner review # Settings → Branches → main → Require review from Code Owners