Conventional Commits: how to write commit messages that are clear, useful and machine-readable

Having a convention for commit messages makes a project’s history much easier to read, and it also lets you automate things like the changelog, versioning or deploys. Here’s the Conventional Commits 1.0.0 spec, explained so you can start using it right away.
The shape of a commit message
<type>[optional scope]: <description>
[optional body]
[optional footer]
Examples
feat: add filter-based search
fix: correct validation error on the form
feat(api)!: drop support for v1
docs: update the install section in the README
The main types
feat: adds a new feature (a minor version in SemVer).fix: fixes a bug (a patch version in SemVer).BREAKING CHANGE: breaks backwards compatibility (a major version in SemVer).
There are two ways to flag a breaking change:
- In the footer:
BREAKING CHANGE: Node 18 is now required- Or with a
!after the type or scope:feat!: drop legacy support
Other common types
Borrowed from the Angular convention:
build: changes to the build setup or external tooling.ci: changes to continuous integration.docs: documentation only.style: formatting, whitespace, semicolons, nothing that changes behavior.refactor: restructuring without changing what the code does.perf: performance improvements.test: adding or fixing tests.chore: small housekeeping with no functional impact (bumping dependencies, for instance).revert: undoing a previous commit.
Style rules
- Write in the imperative:
add,fix,remove. - No period at the end of the subject line.
- Keep the subject short, 50 characters at most.
- If it needs more context, put it in the body.
- Use scopes when they add clarity:
feat(api),fix(auth).
What it’s for
Besides a history that’s easier to read, the convention lets tools do the work for you: automatic changelogs with conventional-changelog, versions and releases with semantic-release, and message validation with commitlint. I explain how to set all that up in From commit to changelog.
Tools
commitlint
Checks that your commits actually follow the convention.
npm install --save-dev @commitlint/cli @commitlint/config-conventional
echo 'npx --no -- commitlint --edit $1' > .husky/commit-msg
Commitizen
An interactive prompt that walks you through writing a well-formed commit.
npm install -g commitizen
npx commitizen init cz-conventional-changelog --save-dev --save-exact
Husky
Runs scripts before commits and pushes: validation, tests, whatever you need.
npm install --save-dev husky
npx husky init
husky init creates the .husky/ folder and adds the prepare script to package.json, so run it before setting up commitlint.
Common questions
What if I use the wrong type?
- If you haven’t pushed it yet,
git rebase -ilets you edit the message. - If you already pushed it, nothing serious happens: you just lose a bit of automation, like that change not showing up in the changelog.
Can I invent my own types?
You can. Just remember that only feat, fix and BREAKING CHANGE feed directly into semantic versioning. Everything else is yours to define.
Does the whole team have to follow it?
Not necessarily. If you work with pull requests and squash & merge, you can clean up the final message at merge time without asking everyone to change how they commit.

