Changelog format: three conventions, three real examples, and how to pick one
Keep a Changelog, Common Changelog and conventional-changelog produce three different CHANGELOG.md files. Three complete changelog examples, a decision table matching convention to project type, and the rule for what belongs in the file at all.
What this solves
Someone is about to create or rewrite CHANGELOG.md and wants to see what the file actually looks like, which convention to follow, and what belongs in it.
How S2P helps
You leave with a CHANGELOG.md structure you can paste in, a convention picked on purpose rather than by accident, and a rule for deciding whether a change belongs in the file.
Key takeaways
- Three conventions cover almost every CHANGELOG.md in the wild: Keep a Changelog, Common Changelog, and the output conventional-changelog generates from Conventional Commits.
- Keep a Changelog 2.0.0 shipped on 2026-06-07 and did not change the format: the same six change types, the same YYYY-MM-DD dates, the same Unreleased and YANKED markers.
- Common Changelog is a stricter subset: four categories instead of six, no Unreleased section, and a required commit or pull request reference on every entry.
- A generated changelog is raw material, not a finished one, because a commit message and a changelog entry are written for different readers.
- The hard part is not the format. It is deciding which changes are notable enough to appear in the file at all.
Section 1
The three conventions, side by side
Almost every CHANGELOG.md follows one of three conventions. Each section below carries a complete changelog example you can paste.
Keep a Changelog defines a changelog as a curated, chronologically ordered list of the notable changes for each version of a project, kept as a file in the repository. Common Changelog adds one prerequisite: every released version has a corresponding git tag. The benefit: a reader decides whether to upgrade without reading your diff.
The three are not variations on formatting. They disagree about who writes an entry and when. Keep a Changelog and Common Changelog assume a person sits down at release time and writes for readers. conventional-changelog assumes a tool reads structured commit messages and emits the file.
That also answers what the different types of changelogs are. Inside the file, the types are the change categories. Outside it, the split is between the repository changelog, the release notes for one version, and a hosted product changelog page. The release notes versus changelog post covers that split.
Keep a Changelog vs Common Changelog vs conventional-changelog output
| Rule | Keep a Changelog 2.0.0 | Common Changelog | conventional-changelog output |
|---|---|---|---|
| Change categories | Six, fixed: Added, Changed, Deprecated, Removed, Fixed, Security | Four, in this order: Changed, Added, Removed, Fixed | Named for commit types: Features, Bug Fixes, and similar |
| Version heading | ## [1.2.0] - 2026-08-28 | ## VERSION - DATE, with the version linked to the release | ## [1.2.0](compare URL) (2026-08-28) |
| Date format | YYYY-MM-DD (ISO 8601) | YYYY-MM-DD (ISO 8601) | YYYY-MM-DD, in parentheses after the version |
| Unreleased section | Recommended, kept at the top | Deliberately removed | Not produced |
| Breaking changes | A Breaking marker on the entry, inside Changed or Removed | A bold Breaking prefix, sorted first within its category | A separate BREAKING CHANGES section |
| Commit and issue references | Optional, as reference-style links at the bottom | Required: every change references a commit | Automatic: a commit hash and issue link per entry |
| Author credit | Optional, and a plain name travels better than a handle | Required, unless the project has one contributor | Not present in the default preset output |
| Yanked releases | A YANKED marker in the heading | A one-sentence notice under the heading | Not represented |
| Who writes an entry | A person, at release time | A person, at release time, with the whole release in view | A tool, from commit messages |
Section 2
Keep a Changelog: the default
The convention most repositories follow and most tools can read. Six change types, one Unreleased section, ISO dates.
Version 2.0.0 shipped on 2026-06-07. Its own release entry states that the six change types, YYYY-MM-DD dates, and the Unreleased and YANKED markers are all unchanged, so an existing file stays valid; only the guidance around the format changed. The site root still serves 1.1.0 as its canonical page, so both numbers are in circulation.
The six types are Added for new features, Changed for changes in existing functionality, Deprecated for soon-to-be removed features, Removed for now removed features, Fixed for bug fixes, and Security for vulnerabilities. There are only six on purpose: the 2.0.0 guidance is explicit that Dependencies is not a type of change, and that a faster JSON parser belongs under Changed, not a new Performance section.
If the old behaviour was a bug, use Fixed; if it was intentional and you are changing it, use Changed. Lead a Security entry with its CVE identifier so security tools can match it to the advisory.
- Changelogs are for humans, not machines.
- Every version should have an entry.
- Group changes of the same type.
- Make versions and sections linkable.
- List the latest version first.
- Show the release date of each version.
- Note which versioning scheme you use.
- Write plainly, because many readers are not native speakers.
CHANGELOG.md (Keep a Changelog 2.0.0)
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `--dry-run` flag for the `deploy` command.
## [2.0.0] - 2026-08-28
Drops the legacy config loader. Files written for 1.x keep working through the shim added in 1.9.
### Changed
- **Breaking:** `parse()` returns a result object instead of raising.
### Removed
- **Breaking:** Node.js 18 support.
### Fixed
- Off-by-one in the pagination cursor.
### Security
- CVE-2026-12345: out-of-bounds read when parsing a malformed header.
## [1.9.0] - 2026-07-14
### Added
- `theme` option for terminal output.
### Deprecated
- The `color` option. Use `theme` instead; `color` is removed in 2.0.0.
## [1.8.3] - 2026-06-02 [YANKED]
### Fixed
- Nothing usable: this build corrupted the on-disk index and was pulled.
[Unreleased]: https://github.com/your/project/compare/v2.0.0...HEAD
[2.0.0]: https://github.com/your/project/compare/v1.9.0...v2.0.0
[1.9.0]: https://github.com/your/project/compare/v1.8.3...v1.9.0
[1.8.3]: https://github.com/your/project/releases/tag/v1.8.3Section 3
Common Changelog: the strict subset
Built for libraries whose changelog gets read out of context, in someone else's dependency bump pull request.
Common Changelog is a style guide adapted from, and a stricter subset of, Keep a Changelog. Four categories replace six: Changed, Added, Removed and Fixed, in that order. Deprecated and Security are gone, and a deprecation is listed under Changed.
In their place come stricter rules. Every change must reference the relevant commit, and should reference the ticket or pull request when one exists, written after the change on the same line, in parentheses. Author names follow the references. Breaking changes carry a bold Breaking prefix and sort first within their category. Entries use the imperative mood and must be self-describing, as if no category heading existed: write Add write() method, not write() method.
There is no Unreleased section: a contributor cannot add the reference to their own change, because it only exists after the fact. Yanked releases get a one-sentence notice rather than a YANKED tag, which the spec concedes is more generic but unparsable. The cost: every entry is hand written at release time, by someone holding the whole release in their head.
CHANGELOG.md (Common Changelog, excerpt from Level/level)
# Changelog
## [10.0.0] - 2025-04-20
_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md)._
### Changed
- **Breaking:** upgrade to abstract-level 3 ([#248](https://github.com/Level/level/issues/248)) ([`b27277c`](https://github.com/Level/level/commit/b27277c)) (Vincent Weevers)
## [8.0.1] - 2024-01-27
### Fixed
- Explicitly depend on abstract-level for TypeScript ([#241](https://github.com/Level/level/issues/241)) ([`c501868`](https://github.com/Level/level/commit/c501868)) (Hanxx).
## [7.0.1] - 2021-10-02
### Added
- Document new features ([#207](https://github.com/Level/level/issues/207)) ([`ad8f924`](https://github.com/Level/level/commit/ad8f924)) (Vincent Weevers)
### Fixed
- Bump dependencies to prevent dedupe ([`7083ec6`](https://github.com/Level/level/commit/7083ec6)) (Vincent Weevers)Section 4
conventional-changelog: the generated file
A tool reads your commit messages and writes the file. The file reads like it.
conventional-changelog generates a changelog from git metadata. Install it alongside a preset and pass the preset name, for example conventional-changelog -p angular, which reads commits since the last matching semver tag and does not overwrite previous changelogs. Its documentation recommends using a commit convention so the tool can tokenize commits. That convention is usually Conventional Commits, whose 1.0.0 specification maps fix to PATCH, feat to MINOR, and any commit carrying BREAKING CHANGE to MAJOR regardless of type.
The output is recognisable, and it is not Keep a Changelog. Version headings are links to a compare diff with the date in parentheses, sections are named Features and Bug Fixes, and breaking changes collect into their own section instead of sitting with the change they break.
Both other conventions argue against generating the file. Keep a Changelog 2.0.0 states that a commit and a changelog entry are written for different people, and that a generated changelog is raw material at best. Common Changelog lists Conventional Commits as an antipattern, because using it still means converting commit messages into a more readable form. The counterargument is short: a generated changelog exists, and an unwritten one does not.
CHANGELOG.md (generated, excerpt from the conventional-changelog repository)
# Changelog
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [8.1.3](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-changelog-v8.1.2...conventional-changelog-v8.1.3) (2026-08-12)
### Bug Fixes
* add conventional-commits-filter to dependencies ([#1536](https://github.com/conventional-changelog/conventional-changelog/issues/1536)) ([43f19df](https://github.com/conventional-changelog/conventional-changelog/commit/43f19dfd521ec46e3a17ff3564b5aebca499d8ea))
## [8.1.0](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-changelog-v8.0.1...conventional-changelog-v8.1.0) (2026-07-09)
### Features
* replace `meow` with `argue-cli` ([#1505](https://github.com/conventional-changelog/conventional-changelog/issues/1505)) ([1ad6177](https://github.com/conventional-changelog/conventional-changelog/commit/1ad6177da0b9da2df4fffc41e0b6caf9b76bf200))
## [8.0.0](https://github.com/conventional-changelog/conventional-changelog/compare/conventional-changelog-v7.2.1...conventional-changelog-v8.0.0) (2026-06-26)
### ⚠ BREAKING CHANGES
* Handlebars template strings and partial files were replaced with render functions.
* packages now require Node.js 22 or newer.Section 5
Which convention to pick
Match the changelog format to who reads the file and how much writing you will do at release time.
The constraint is effort per release, not aesthetics. Common Changelog produces the best file and asks the most of you. conventional-changelog asks almost nothing and produces the weakest file. Keep a Changelog sits between them, and is the safe answer when you are unsure.
Whichever you pick, declare it in the preamble. Keep a Changelog's guidance is that stating your conventions tells readers, and tools, what to expect. Pin the link to the version you follow so it stays accurate as that page changes. Neither spec needs a changelog tool installed to start.
Convention by project type
| Project type | Start with | Why | What it costs |
|---|---|---|---|
| Public library or SDK | Common Changelog | Entries get read out of context, in dependency bump pull requests on other people's repositories | Every entry is hand written and needs a commit or pull request link |
| Application or CLI with end users | Keep a Changelog | Deprecated and Security are their own types, and users of an installed app act on both | Someone curates the Unreleased section before every release |
| Public API or hosted service | Keep a Changelog, with a Deprecated entry for every planned removal | Deprecating in one release and removing in a later one is the only warning integrators get | You must announce a deprecation at least one release before you act on it |
| Internal service | conventional-changelog output | Nobody outside the team reads it, and a generated log beats an unwritten one | Entries are reworded commit messages, so file quality tracks commit quality |
| Monorepo | One changelog per component, plus one central summary | Readers should not have to read a dozen component files to understand a release | Two levels to keep in sync at release time |
| Pre-1.0 project | Keep a Changelog, Unreleased section only at first | Under 0.y.z anything may change at any time, so the file is your only compatibility signal | Almost nothing, and it builds the habit before the first stable release |
Section 6
What belongs in the file, and what does not
The specs agree on the format and mostly leave this to you. It decides whether anyone reads the file twice.
Keep a Changelog 2.0.0 frames this as curating rather than accumulating: a changelog records notable changes, which means some changes are not notable. It warns the other way too. A changelog that records only some changes can mislead as much as no changelog, because readers treat it as the full picture.
The test that resolves most cases: would someone who uses the distributed software notice, or need to act? Common Changelog turns that into an exclusion list, and a shorter list of things that look like noise and are not.
Two habits do the most damage. Ignoring deprecations: mark something Deprecated in one release and only Removed in a later one, and say which version will remove it. And pasting a commit list, which records steps in the evolution of source code, not the differences that matter to the people using it.
- Leave out: dotfile changes, development-only dependency bumps, minor code style changes, formatting changes in documentation.
- Keep in: refactors, because they may have unintentional side effects.
- Keep in: changes to supported runtime environments, even when they only show up in dotfiles.
- Keep in: code style changes that use new language features.
- Keep in: new documentation, when the feature was previously undocumented.
- Keep in: every deprecation, removal and breaking change. If you record nothing else, record these.
Section 7
Version headings, dates and the SemVer mapping
The heading carries the version, the date and the diff link. Get the SemVer mapping right and the heading does most of the communicating.
Write the heading as the version number and the date in YYYY-MM-DD form. Keep a Changelog's reasons for ISO order: it runs from the largest unit to the smallest, avoids regional formats where month and day swap places, and is an ISO standard. Common Changelog requires the same format.
The square brackets around the version make it a Markdown reference link, resolved once at the bottom of the file. Point each version at a comparison with the one before it, and Unreleased at HEAD, so every version is tied to its tag and links to the exact diff. Any host exposes tag and comparison URLs, so the pattern survives a move to a different one.
Semantic Versioning 2.0.0 decides which number moves. Under 0.y.z the spec says anything may change at any time and the public API should not be considered stable, which is when a changelog is doing the most work.
Change type to version bump
| What changed | Keep a Changelog type | SemVer 2.0.0 bump |
|---|---|---|
| Backward compatible bug fix | Fixed | PATCH |
| New backward compatible functionality in the public API | Added | MINOR |
| Public API functionality marked as deprecated | Deprecated | MINOR |
| Behaviour changed but still compatible | Changed | MINOR, or PATCH when the change is internal only |
| Backward incompatible change or removal | Changed or Removed | MAJOR |
| Vulnerability fixed | Security | PATCH when the fix is backward compatible, MAJOR when it is not |
Section 8
Where to store it, and is changelog one word
Repository root, one file, called CHANGELOG.md. The rest is edge cases.
Common Changelog makes the location a hard requirement: the filename must be CHANGELOG.md and the content must be Markdown starting with a first-level heading. Keep a Changelog allows HISTORY, NEWS or RELEASES but says a predictable name makes it easy to find. There is no special GitHub changelog format: a GitHub changelog is CHANGELOG.md at the repository root, with version headings resolved to GitHub compare URLs at the bottom.
Is changelog one word or two? One word. Keep a Changelog records the decision in its own 1.0.0 entry: start using changelog over change log, since it is the common usage. Write the file name in uppercase and the word itself in lowercase in prose.
In a monorepo, unrelated projects sharing a repository each keep their own file, while one product made of many parts can keep a changelog per component but should also keep one central changelog. A hosted product changelog page, or an app changelog inside the product, is a separate artifact generated from this file rather than replacing it.
Section 9
Keeping it current without automating the wrong half
The maintenance loop is short. The common failure is handing a machine the judgement instead of the mechanics.
Land changes, add the notable ones to Unreleased, and at release time rename Unreleased to the new version in both the heading and its link, add the date, then add a fresh empty Unreleased pointing at HEAD. Common Changelog adds one ordering detail worth copying: commit the changelog entry before you create the git tag, so the tag contains its own entry.
Keep a Changelog 2.0.0 says to use continuous integration for mechanical tasks: moving Unreleased into a dated version, checking formatting, reminding a contributor that a change may need an entry. Do not make a changelog edit a required check on every pull request, because that teaches people to add a line to pass the check, which fills the file with noise.
Because every version sits under a predictable heading, that section is already the draft of its release notes, and a few lines of awk pull it out.
extract-release-notes.sh
#!/usr/bin/env bash
# Print the CHANGELOG.md section for one version, ready to paste into a release.
set -euo pipefail
VERSION="${1:?usage: extract-release-notes.sh 1.2.0}"
awk -v want="$VERSION" '
/^## / {
if (found) exit
head = $0
gsub(/[][]/, "", head)
split(head, f, " ")
if (f[2] == want) { found = 1; next }
}
found { print }
END { if (!found) { print "no entry for " want > "/dev/stderr"; exit 1 } }
' CHANGELOG.md
# ./extract-release-notes.sh 1.2.0 > notes.mdSection 10
After the file is written
The one step that still repeats every release.
Once CHANGELOG.md is the source of truth, the work that repeats every release is turning one version section into posts for wherever your users are. Ship 2 Post watches your repository for releases, tags, merged pull requests and deployments through its GitHub App, drafts channel-native variants in a brand voice you configure, and queues them for one-click approval before anything publishes to any of its 14 destinations. The free plan is one post per UTC day, no credit card.
FAQ
Questions this article answers
What is the standard format for a changelog?
There is no formal standard, and Keep a Changelog says so directly rather than claiming to be one. The de facto format is Keep a Changelog: a CHANGELOG.md at the repository root, a Changelog heading and a short preamble naming your conventions, versions newest first under a heading of version number and ISO date, and entries grouped under Added, Changed, Deprecated, Removed, Fixed and Security.
What should be included in a changelog?
Notable, user-facing changes, grouped by type and by version. Always include deprecations, removals, breaking changes and security fixes. Leave out dotfile edits, development-only dependency bumps and minor code style tweaks. Keep refactors, changes to supported runtime environments, and newly written documentation for a previously undocumented feature, because all three can surprise somebody upgrading even though they look like housekeeping.
Where should a changelog be stored?
In a file named CHANGELOG.md at the root of the repository, written in Markdown. Common Changelog requires that exact filename. Keep a Changelog notes that some projects use HISTORY, NEWS or RELEASES but recommends a predictable name so people can find it. Keeping it in the repository means it travels with your code, unlike release pages that live in a host's database.
Is changelog one word or two?
One word. Keep a Changelog records the switch in its own 1.0.0 changelog entry: start using changelog over change log, since it is the common usage. Write the file name in uppercase as CHANGELOG.md, and the word itself in lowercase when you use it in a sentence. Change log as two words is not wrong, just dated.
How do you decide if a change is significant enough to include in the changelog?
Ask whether someone who uses the distributed software would notice the change or need to act on it. If yes, it goes in. Keep a Changelog frames this as restraint: a changelog records notable changes, so by definition some changes are not notable. It also warns that a changelog recording only some changes misleads, because readers assume it is complete. Trivial out, notable in, consistently.
How to generate changelog automatically?
Conventional changelog generators read structured commit messages to draft the file and pick the next version: conventional-changelog, semantic-release, release-please, Changesets and git-cliff. GitHub also documents generating release notes from a default template. Treat all of it as a first draft: Keep a Changelog 2.0.0 calls a generated changelog raw material at best. The guide to automating release notes on GitHub compares the tools in detail.
Related guides and pages
Where to go next
Hand-picked pages that go deeper on the workflow, channels, and tooling covered above.
