Guide
Conventional Commits to Changelog: The Complete Guide
Conventional Commits turn commit messages into structured data so tools can generate a changelog automatically, mapping feat to features, fix to bug fixes, and footers to breaking changes.
Updated 2026-06-30
How do Conventional Commits generate a changelog?
Conventional Commits generate a changelog by giving every commit message a structured prefix that tools can parse, so a 'feat:' commit becomes a feature entry, a 'fix:' commit becomes a bug fix entry, and a commit that declares a breaking change triggers a major version bump and a Breaking Changes section. Because the meaning is encoded in the message itself, a generator can build a grouped, versioned changelog with no manual sorting.
This is the core value: you do the small work of writing a structured commit once, and the tooling does the larger work of categorizing, versioning, and assembling the changelog forever after.
The Conventional Commits format
A Conventional Commit follows the shape 'type(scope): description', with an optional body and footers. The type is the part tools care about most, because it determines where the change lands in the changelog and how it affects the version number.
- •feat: a new feature (maps to a minor version bump).
- •fix: a bug fix (maps to a patch version bump).
- •docs: documentation only changes.
- •refactor: code changes that neither fix a bug nor add a feature.
- •perf: a performance improvement.
- •test: adding or correcting tests.
- •chore: build process, tooling, or dependency changes.
- •scope: an optional noun in parentheses, e.g. feat(auth):, indicating the area changed.
Declaring breaking changes
There are two ways to mark a breaking change. Add an exclamation mark after the type, as in 'feat!: drop support for Node 16', or add a 'BREAKING CHANGE:' footer describing what broke and how to migrate. Either signal tells the generator to bump the major version and to surface the change prominently.
Example: a commit body ending with 'BREAKING CHANGE: the apiToken option was renamed to apiKey' lands in the Breaking Changes section of the next release and forces a major version bump, for example from v2.4.0 to v3.0.0.
From commits to a generated changelog
With Conventional Commits in place, a generator walks the commits since the last tag, groups them by type, and writes a changelog section for the new version. Here is the typical flow in a CI pipeline.
- 1
Write structured commits
Every merge to the main branch uses a Conventional Commit message, ideally enforced by a commit linter so nothing slips through.
- 2
Run the generator
A tool such as release-please or a semantic-release workflow reads the commits since the previous tag.
- 3
Determine the next version
The tool computes the version bump from the commit types: a breaking change means major, a feat means minor, a fix means patch.
- 4
Group and write the changelog
Commits are sorted into Features, Bug Fixes, and Breaking Changes sections and prepended to CHANGELOG.md under the new version heading.
- 5
Tag and release
The tool creates the Git tag and the release, closing the loop from commit message to published changelog.
Tips for clean output
The changelog is only as good as the commit messages, so invest in the inputs. A few habits make a large difference in readability.
- •Write the description as the changelog line you want readers to see.
- •Enforce the format with a commit-message linter in CI.
- •Squash-merge pull requests so each PR becomes one clean commit.
- •Keep one logical change per commit so categorization stays accurate.
- •Use scopes consistently so readers can tell which area changed.
Beyond the raw changelog
A Conventional Commits changelog is precise but still written in developer voice. If your audience includes non-technical users, a second step can rewrite that structured history into benefit-first prose. AI tools, including release-notes.dev, read the same commits and pull requests and produce a polished, hosted changelog page, while the generated CHANGELOG.md remains the canonical record in the repo.