Conventional Commits is a specification for writing commit messages in a structure that both humans and machines can parse: type(scope): short description. The payoff is not a tidier git log for its own sake — it is that tooling can read your commit history and automatically generate changelogs, decide the next semantic version, and flag breaking changes, without a human writing release notes by hand. Once it is enforced consistently, a release becomes a mechanical process instead of a meeting.
What changed in 2026
- Enforcement moved earlier in the pipeline. Most teams validate commit messages with commitlint at commit time via a Git hook, rather than catching bad messages in CI after the fact.
- Changesets became the standard for monorepos, working alongside or instead of pure Conventional Commits for packages that need independent version numbers.
- AI commit message generators, built into IDEs and CLI tools, now default to Conventional Commits formatting, making adoption close to zero-effort for individual contributors.
- Squash-merge workflows made the PR title the commit message, so many teams now enforce the convention on PR titles rather than on every individual commit.
The commit format
Every commit message starts with a type, an optional scope, and a short description:
<type>(<optional scope>): <description>
<optional longer body>
<optional footer(s)>
Examples:
feat(auth): add JWT refresh token rotation
fix(api): return 404 instead of 500 for missing resource
feat(payments)!: require idempotency key on all charge requests
BREAKING CHANGE: charge endpoint now rejects requests without
an Idempotency-Key header.
Tools like semantic-release and changesets read this structure directly:
| Type |
Meaning |
Version bump |
feat |
New feature |
Minor |
fix |
Bug fix |
Patch |
docs |
Documentation only |
None |
style |
Formatting, no logic change |
None |
refactor |
Code change, no feature or fix |
None |
perf |
Performance improvement |
Patch |
test |
Adding or fixing tests |
None |
chore |
Tooling, deps, build config |
None |
! or BREAKING CHANGE: footer |
Incompatible API change |
Major |
A feat bumps minor, a fix bumps patch, and a BREAKING CHANGE footer or ! bumps major, all without a human deciding the version number.
Setting it up
- Install commitlint with the conventional config:
npm install --save-dev @commitlint/cli @commitlint/config-conventional.
- Add a config file (
commitlint.config.js) that extends @commitlint/config-conventional.
- Wire it to a Git hook with Husky:
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'.
- Add semantic-release or changesets to consume the history and automate versioning and changelog generation on merge to main.
- Enforce PR titles too if you squash-merge, since the squashed commit typically takes the PR title as its message.
Common mistakes
Writing a type that does not match the change. Marking a new feature as fix breaks the automated version bump — it ships as a patch when it should be minor.
Forgetting the BREAKING CHANGE footer. Without it, a genuinely incompatible change ships as a minor or patch version, and downstream consumers upgrade into a break they were not warned about.
Enforcing it only in CI, not at commit time. Catching a malformed message after five more commits have been written on top of it means a painful interactive rebase to fix history. A commit-msg hook catches it immediately.
Applying it inconsistently across a monorepo. If some packages use Conventional Commits and others do not, automated changelog and version tooling produces gaps and inconsistent release notes.
FAQ
Do I need Conventional Commits for a solo project?
Not really. The value comes from automation across a team — changelogs, semantic versioning, and release notes generated without a human writing them. A solo project gets less benefit from the ceremony.
What is the difference between fix and refactor?
fix changes behavior to correct a bug; refactor changes code structure without changing behavior. If tests would need to change to describe new expected behavior, it is a fix or feat, not a refactor.
How does this relate to semantic versioning?
Conventional Commits gives tooling like semantic-release a machine-readable signal for exactly the decision semver requires: is this a breaking, additive, or bug-fix change. That maps directly to major, minor, and patch.
Can I use Conventional Commits with squash-and-merge?
Yes, and it is common — enforce the format on the PR title, since GitHub uses it as the default squashed commit message.
Where to go next
See how to squash commits in 2026 for keeping history clean before these conventions matter, code review best practices in 2026 for catching mismatched commit types before merge, and Nx vs Turborepo in 2026 if you are applying this convention across a monorepo with per-package releases.