Conventional commits cheat sheet, on one screen
The format, the types, breaking changes, footers and what commitlint rejects, in tables you can scan while you type. Every commit in the copy section passes commitlint’s default config, because I ran them all through it. The reasoning behind each row lives in the conventional commits guide.
Definition
What this covers
Conventional Commits is a format for the first line of a commit message: a type, an optional scope in parentheses, a colon and a space, then a short description. You use it when you want a tool to pick the next version number and write the changelog from your git history. fix means a patch release, feat means a minor release, and a breaking change means a major one. The other types sort the changelog and carry no version meaning under the spec, which is at version 1.0.0. This sheet is the short form: the shape, the types, both ways to mark a breaking change, footers, what commitlint’s default config rejects, and ten commits you can copy. When a row needs explaining, like why semantic-release ignores feat! out of the box, the guide linked above has it.
Free tool
Check a commit
Paste a message or a few lines of git log --oneline. It runs in your browser against the spec and commitlint’s default rules.
Commit checker
release-please
2.0.0 major
A breaking change.
semantic-release, default config
1.5.0 minor
At least one feat. A "!" commit was skipped.
feat(search): find frogs by locationfeatscope: searchspec: minor bumpchangelog: Features
Valid under the spec and commitlint’s default config.
fix: stop the map jumping on zoomfixspec: patch bumpchangelog: Bug Fixes
Valid under the spec and commitlint’s default config.
feat!: drop the v1 frogs endpointfeatbreakingspec: major bumpchangelog: BREAKING CHANGES
Valid under the spec and commitlint’s default config.
chore: bump eslintchorespec: no version meaningchangelog: Miscellaneous Chores (hidden)
Valid under the spec and commitlint’s default config.
docs: Fix typo in README.docsspec: no version meaningchangelog: Documentation (hidden)
- commitlint · subject-case commitlint rejects a description that starts with a capital letter. Lower-case the first word.
- commitlint · subject-full-stop No full stop at the end of the description.
This tells you what the release tools will do with your commits. Writing the post about the release is a separate job, and that one I automated.
Format
The header
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]- Colon, then exactly one space.
feat:add xfails commitlint. - Scope is optional. Empty parentheses pass commitlint, but release-please can’t parse
feat(): x, so drop them. - No space before the colon.
feat : xfails commitlint and release-please. - One blank line before the body, one before the footers.
- The spec says types aren’t case sensitive. commitlint’s default config wants them lower case, so write them lower case.
Reference
Types
Only feat and fix come from the spec. The rest are the Angular list that commitlint enforces by default. The section column is where release-please puts each type; “hidden” means left out of the changelog.
| Type | When | Bump | Changelog section |
|---|---|---|---|
feat | A new feature someone can use | minor | Features |
fix | A bug fix | patch | Bug Fixes |
perf | Faster, same behaviour | none (tools: patch) | Performance Improvements |
revert | Undoes an earlier commit | none | Reverts |
docs | Documentation only | none | hidden |
refactor | Code change, no behaviour change | none | hidden |
test | Tests only | none | hidden |
build | Build system or dependencies | none | hidden |
ci | CI configuration | none | hidden |
style | Formatting, whitespace | none | hidden |
chore | Anything else that ships nothing | none | hidden |
A release made only of hidden types doesn’t happen. Want a type that isn’t on the list? Add it to your commitlint config or the linter will reject it.
Major releases
Breaking changes
Either a ! before the colon:
feat(api)!: return frogs sorted by nameOr a footer:
feat(api): return frogs sorted by name
BREAKING CHANGE: clients that relied on insertion order must sort.- Under the spec, the two mean the same thing, and you can use both at once.
- The footer must be capitals.
BREAKING-CHANGE:with a hyphen also counts under the spec. - semantic-release with no config doesn’t understand
!and releases nothing for that commit. The semantic-release guide has the two-line fix.
The linter
commitlint default rules
What @commitlint/config-conventional 21.2.3 enforces. Errors fail the commit. Warnings print and let it through.
| Rule | Level | Checks | Fails it |
|---|---|---|---|
type-enum | error | Type is one of the 11 in the table above | wip: half done |
type-case | error | Type is lower case | Fix: login button |
type-empty | error | There is a type | Add login |
subject-empty | error | There is a description | feat:add login |
subject-case | error | Description does not start with a capital | feat: Add login |
subject-full-stop | error | No full stop at the end | fix: stop the double submit. |
header-max-length | error | First line is 100 characters or fewer | a 106-character header |
header-trim | error | No whitespace before or after the first line | " feat: add login" |
body-max-line-length | error | Body lines are 100 characters or fewer | a 120-character body line |
footer-max-line-length | error | Footer lines are 100 characters or fewer | a 120-character footer line |
body-leading-blank | warning | Blank line between header and body | body on line 2 |
footer-leading-blank | warning | Blank line before the footers | Refs: #1 on line 2 |
subject-case is the one that catches everyone. Any description starting with a capital fails, including OAuth token refresh. Merge commits, Revert "...", fixup! and bare version numbers are skipped by default.
Copy these
Commits to copy
| Commit | Result |
|---|---|
feat(search): find frogs by location | minor, under Features |
fix: stop the map jumping on zoom | patch, under Bug Fixes |
perf(images): serve smaller portraits on mobile | patch in both release tools |
docs: explain the release process in CONTRIBUTING | no release, hidden |
refactor(auth): move the session check into its own module | no release, hidden |
test: cover the empty search case | no release, hidden |
build(deps): bump eslint to 9 | no release, hidden |
ci: run the tests on node 24 | no release, hidden |
A fix with a body and two footers:
fix(auth): stop the double submit
The button fired twice on slow connections.
Refs: #42
Reviewed-by: SamA revert. release-please lists it under Reverts and cuts a patch; semantic-release with no config releases nothing:
revert: feat(search): find frogs by location
Refs: a1b2c3dIf you’d rather be asked questions than remember any of this, Commitizen writes the message for you.
Honesty
What this doesn’t fix
Good commits get you the right version and a changelog. They don’t get anyone outside the repo to hear about the release. The changelog waits for people who go looking, and nobody goes looking.
That’s the part I built Merge & Tell for. It reads each merged pull request and drafts a post about it, and I read every one before it goes out. The updates page is it running on itself.
The commit is tidy. Nobody has read it yet.
You were going to merge the PR anyway.