Conventional Commit Format: feat, fix, and Why It Powers Auto-Changelogs
A practical guide to the Conventional Commit format: how feat, fix, chore, scope, and breaking changes drive automated changelogs and semantic version bumps.
Conventional Commit Format: feat, fix, and Why Structure Beats Prose
I used to write commit messages like diary entries. "fixed the thing", "more tweaks", "ok this should work now". They were fine for me on a Tuesday afternoon and useless to anyone reading git log three months later, including future me. The day a release pipeline failed because it could not figure out whether my changes deserved a patch or a minor bump, I switched to the Conventional Commits format and never looked back. The structure costs ten extra seconds per commit and pays you back every time a machine reads your history.
What the Conventional Commits spec actually says
Conventional Commits 1.0.0 defines a small, strict grammar for the first line of a commit. The shape is:
type(scope): description
The spec requires a noun type followed by an optional scope in parentheses, then a colon, then exactly one space, then the description. It also reserves two type names with special meaning: feat, which "MUST be used when a commit adds a new feature", and fix, which "MUST be used when a commit represents a bug fix". Everything after that is convention rather than law: teams commonly add docs, style, refactor, perf, test, build, ci, chore, and revert.
The spec is deliberately minimal. It does not tell you how long the description should be or what tense to use. Those are community norms layered on top: keep the header at or under roughly 72 characters, write the description in lowercase imperative mood, and drop the trailing period. The reason for the imperative mood is that a commit completes the sentence "this commit will...", so "add passkey login" reads correctly while "added passkey login" does not.
Why feat and fix are not just labels
Here is the part that turns a cosmetic convention into real leverage. Semantic-versioning tooling maps commit types directly onto version bumps:
- A
fixcommit triggers a PATCH bump:1.4.0becomes1.4.1. - A
featcommit triggers a MINOR bump:1.4.0becomes1.5.0. - A breaking change triggers a MAJOR bump:
1.4.0becomes2.0.0.
That mapping is the whole point. When you tag a commit feat, you are not just describing it; you are casting a vote about the next release number. A tool like semantic-release reads the commits since the last tag, finds the highest-priority change, computes the next version, writes the git tag, and generates a changelog grouped by type, all with no human deciding the number. If you want to reason about what a given set of commits means for a version, a tool like the semver increment helper shows exactly how each bump type moves the number.
This is also why consistency matters more than perfection. If half your team writes feat and the other half writes feature or Add feature, the parser misses commits, the changelog has holes, and the version math is wrong. A generator that enforces the exact tokens removes that whole category of mistake.
Scope and breaking changes: the two modifiers that matter
The scope is an optional noun in parentheses naming the part of the codebase you touched: (api), (auth), (billing). It earns its keep in a monorepo or a large app, where a bare fix could land anywhere. Compare fix: round tax against fix(billing): round tax to cents. The second one tells a reviewer where to look before they open the diff. Keep scopes short, reuse the same set across the team, and leave the scope off entirely when a change is genuinely project-wide.
A breaking change is the high-stakes modifier. The spec gives you two ways to signal it, and serious tooling expects both. First, put a ! immediately before the colon: feat(api)!: drop v1 endpoints. Second, add a footer line beginning with BREAKING CHANGE: that explains what broke and how to migrate. The ! alone is a visible hint, but release automation reads the BREAKING CHANGE: footer to force the MAJOR bump. Skipping the footer is one of the most common ways a breaking change quietly ships as a minor release and surprises everyone downstream.
A real input and output, start to finish
Let me run one through. Say I just added passkey login to the auth module and I want the next release grouped under Features with a minor bump.
I open the Conventional Commit Generator, pick feat from the type dropdown, set the scope to auth, and type the description add passkey login support. The live preview assembles:
feat(auth): add passkey login support
Now suppose this also removes the old password endpoint. I toggle the breaking switch and write a migration note in the footer field. The output becomes a full, parser-ready message with the blank line the spec requires between header and footer:
feat(auth)!: add passkey login support
BREAKING CHANGE: password login removed; migrate clients to /v2/passkey
The tool validates as I type. If I had written Add passkey login support. with a capital A and a trailing period, it would flag both, because the header is a title, not a sentence. Paste that into git commit, and semantic-release knows to cut a major version and put the breaking note at the top of the changelog.
Footers, issues, and a workflow that compounds
The footer block does more than carry breaking-change notes. A footer line like Closes #214 tells git and GitHub to close issue 214 when the commit merges, which makes every commit traceable back to the report that prompted it. You can stack footers: a Closes #214, a Reviewed-by: line, a Refs: to a design doc. They all sit after a blank line below the body, and parsers read them as key-value metadata.
The compounding benefit shows up over months. Once your history is structured, the changelog writes itself, the version number is never a debate, and git log --oneline becomes a readable feature timeline instead of a wall of "wip". Pair the commit convention with a clean repo setup, a tidy .gitignore and a disciplined branch flow, and the boring parts of release management stop needing a human in the loop.
You do not have to memorize the spec to get there. Pick the type, fill the scope, write one lowercase line, and let the validation catch the rest. The first ten commits feel like overhead. By the fiftieth, you stop noticing, and your tooling starts doing work you used to do by hand.
Made by Toolora · Updated 2026-06-13