How to Generate a Changelog from Conventional Commits
Conventional Commits give every commit a machine-readable type — feat, fix, docs — plus an optional scope and a BREAKING CHANGE marker. That is exactly the structure a changelog generator needs, which is why nearly every changelog CLI in the ecosystem is built around it. Here is the format in 30 seconds, two working generator setups, and what to do when the discipline slips.
The format in 30 seconds
A conventional commit is type(scope): description, where type is one of feat, fix, docs, style, refactor, perf, test, build, ci, chore, or revert. Two optional extensions matter for changelogs:
- Breaking marker. A
!before the colon (feat!:) or aBREAKING CHANGE:footer in the body marks a breaking change. - Scope. The part in parentheses, when useful —
feat(export): add CSV support.
feat(export): add CSV export for reports
fix(auth): keep session alive across tab refresh
feat!: API key rotation now invalidates old keys immediately
BREAKING CHANGE: rotated keys no longer have a 24h grace period
The generator maps types to sections: feat to Added, fix to Fixed, perf or refactor to Changed (often filtered out), and BREAKING CHANGE to its own flagged section. If your commits do not follow the format, the generator has nothing to map — which is the whole ballgame, honestly.
Setup 1: git-cliff in CI
git-cliff is a free Rust CLI that renders a CHANGELOG.md from git history, targeting the Keep a Changelog format by default. A minimal config:
# cliff.toml
[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^perf", group = "Changed" },
{ body = "BREAKING CHANGE", group = "BREAKING CHANGES" },
]
git cliff -c cliff.toml --prepend CHANGELOG.md
Run it in CI on tag push and commit the result. You get a spec-shaped CHANGELOG.md with zero paid dependencies. The known trade-off: it reads commits, so it inherits whatever your commit messages contain — including the "fix typo" noise you would rather not show users.
Setup 2: standard-version for release automation
standard-version is a CLI that reads conventional commits, bumps the version according to semver (feat to minor, breaking to major, fix to patch), writes the changelog entry, and tags the release — one command per release:
npx standard-version
Pair it with a CI step that publishes the tagged release, and the version number, changelog, and git tag can never disagree with each other. This is the classic open-source release flow, and it is well-trodden.
When the discipline slips
In practice, conventional-commit discipline decays: drive-by commits, hotfixes pushed without review, chore: stuff. The failures look the same — the generated changelog is either full of junk or missing real changes. Three fallbacks, in order of effort:
- Filter harder.
filter_unconventional = trueplus a parser whitelist already drops the worst; extend the whitelist to the types your users care about. - Enforce at the PR gate. A commitlint config rejects non-conventional commit titles on merge. The changelog stays clean because the commits do.
- Switch the source to PRs. Generate from merged pull requests (titles plus labels) instead of commits. PR titles tend to be cleaner than commit messages, and labels replace commit types. This is the route most SaaS products end up on; see how to automate your changelog from pull requests.
ShipChangelog
If the commit-level discipline is not worth enforcing, ShipChangelog skips the layer: it generates entries from merged PRs (titles, labels, and BREAKING CHANGE markers) rather than commit messages, flags breaking changes, and publishes the result as a hosted changelog with three audience versions instead of a file in the repo. Free for one repo with 5 entries a month.
FAQ
Do I need 100% conventional commits to make this work?
No — you need a high enough ratio that the generated sections are trustworthy. Most teams get there with commitlint on pull requests plus a filter that drops unconventional commits; the generator's whitelist then does the rest.
Conventional commits or PR labels — which is the better source?
Commits are more granular and free; PR labels are cleaner and closer to what users care about. If your repo already has healthy PR titles and labels, generate from PRs. If you maintain a library where the commit log is the artifact, stay with commits.
Where should the generated changelog live?
For a library: in the repo, as CHANGELOG.md — the readers are developers. For a product: on a hosted page with RSS and email, because customers do not read your repo. Many teams do both, and the git-cliff vs ShipChangelog comparison maps when each half is worth it.
ShipChangelog writes these changelogs for you — start free with 1 repo.