Conventional Commits Explained
Dev Nexus5 min read
What Conventional Commits are, the full list of types, and why teams adopt the spec to automate versioning and changelogs.
If you've browsed a modern open-source repo, you've seen commit messages like feat(auth): add password reset and fix: handle empty response. That prefix isn't decoration — it's the Conventional Commits specification, a small set of rules that make your Git history readable by both humans and machines.
This guide explains the format, walks through the standard types, and shows why teams adopt it: once your commits follow the spec, tools can derive version numbers and changelogs from them automatically. No more hand-maintained release notes.
The Problem
Left to their own devices, a team's commit history becomes a patchwork of personal styles. One person writes Fixed the thing, another update, a third WIP more auth. Nothing is wrong with any of them individually, but together they form a history you can't skim and can't automate.
The pain is sharpest at release time. To write a changelog, someone has to read through dozens of commits and manually sort them into features, fixes and breaking changes. To pick the next version number, someone has to decide by hand whether the release is a patch, a minor or a major. Both jobs are tedious, error-prone, and easy to get wrong — a breaking change slips into a "minor" release and downstream users' builds break.
The root cause is that unstructured messages carry no machine-readable signal about what kind of change each commit is.
The Solution
Conventional Commits fixes this by giving every commit subject a lightweight, structured prefix:
<type>[optional scope][!]: <description>
For example: feat(api): add pagination to search endpoint. The type classifies the change, the optional scope in parentheses names the affected area, an optional ! flags a breaking change, and the description is a normal imperative subject line.
Because the type is now explicit, tools can read it. fix maps to a patch release, feat to a minor release, and a breaking change (! or a BREAKING CHANGE: footer) to a major release — exactly the rules of semantic versioning. Release automation like semantic-release reads your commits, bumps the version, and generates a grouped changelog with zero manual effort.
You don't have to memorise the format to comply. The Commit Message Generator lets you pick a type and scope and produces a spec-compliant message; if you're new to writing commits at all, start with How to Write Better Git Commit Messages.
Step-by-Step Guide
- 1
Start with the type
The two core types are
feat(a new feature, triggers a minor version bump) andfix(a bug fix, triggers a patch). Beyond those, common types includedocs,style,refactor,perf,test,build,ci, andchore. Pick the one that best classifies the change — the type is the part tools actually read. - 2
Add a scope when it helps
In parentheses after the type, name the part of the codebase you touched:
feat(parser):,fix(auth):,refactor(api):. Scope is optional but valuable in larger projects — it lets readers and changelog tools group commits by area. Keep your scope names consistent across the team. - 3
Write the description in the imperative
After the colon and a space, write a short imperative summary:
feat(cart): add quantity stepper. Same rules as any good subject line — imperative mood, roughly undefined characters, specific rather than vague. The type and scope don't excuse a lazy description. - 4
Flag breaking changes explicitly
A change that breaks backward compatibility needs a signal, or automation will under-bump the version. Add a
!before the colon —feat(api)!: remove deprecated v1 routes— and/or aBREAKING CHANGE:footer in the body describing the impact. Either triggers a major version bump. - 5
Enforce it in your workflow
Consistency is what makes the spec pay off. Teams commonly add a commit-lint check (like commitlint) as a Git hook or CI step so non-conforming messages are caught early. Combined with a generator for drafting, the format quickly becomes second nature rather than a chore.
Common Mistakes
Using the wrong type
Labelling a genuine feature as
fix— or a breaking change as a plainfeat— corrupts your automated versioning. Since tools trust the type, a mislabelled commit can ship a breaking change as a minor release. Match the type to what the change really is.Forgetting the breaking-change marker
The most costly mistake: shipping an incompatible change without a
!orBREAKING CHANGE:footer. Automation then treats it as an ordinary feature or fix, downstream users auto-update, and their builds break. Always flag breaking changes.Treating the prefix as the whole message
fix:followed bystuffis still a useless message. The convention adds structure on top of a good subject line — it doesn't replace one. Write a clear, specific description after the type.Inventing inconsistent scopes
auth,Auth,authenticationandloginused interchangeably fragment your history and confuse changelog grouping. Agree on a small, stable set of scope names and stick to them across the team.
Frequently Asked Questions
What is the Conventional Commits format?
It's `<type>[optional scope][!]: <description>`, for example `feat(auth): add password reset`. The type classifies the change, the scope names the affected area, an optional `!` marks a breaking change, and the description is a short imperative summary.
What are the main commit types?
The core two are `feat` (a new feature) and `fix` (a bug fix). Other common types are `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, and `chore`. Only `feat` and `fix` affect version bumps by default; the rest classify non-functional work.
How do Conventional Commits relate to semantic versioning?
They map directly: `fix` triggers a patch release, `feat` a minor release, and a breaking change (a `!` or a `BREAKING CHANGE:` footer) a major release. This lets tools compute the next version number automatically from your commit history.
How do I mark a breaking change?
Add a `!` before the colon, like `feat(api)!: drop v1 endpoints`, and/or include a `BREAKING CHANGE:` footer in the commit body explaining the impact. Either signal tells release tooling to bump the major version.
Do I have to adopt Conventional Commits to write good commits?
No — a clear imperative subject and a why-focused body are good on their own. Conventional Commits adds a machine-readable layer that enables automated versioning and changelogs, which pays off most on teams and libraries with regular releases.
Try the Tool
Commit Message
Pick a type and scope and get a spec-compliant commit message instantly.
Related Tools
Related Articles
How to Write Better Git Commit Messages
A practical guide to writing Git commit messages your future self will thank you for — the right structure, tense, and level of detail.
Read articleAre UUIDs Really Unique?
In practice UUIDs never collide - here is the math behind why, and the one thing people get wrong: a UUID is an identifier, not a secret.
Read articleencodeURI vs encodeURIComponent Explained
The clear difference between encodeURI and encodeURIComponent, with examples showing which one to use for a value versus a whole URL.
Read article