Changelog basics
What is it?
A changelog is a curated, chronologically ordered list of the notable changes for each version of a project.
Why keep one?
To make it easy for users and contributors to see the notable changes between each version.
Who needs this?
People do. Anyone who uses or builds software wants to know what changed, and why.
How do I keep a good changelog?
Guiding principles
- 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. Many of your readers are not native speakers, so favor clear, concise wording.
Types of changes
Addedfor new features.Changedfor changes in existing functionality.Deprecatedfor soon-to-be removed features.Removedfor now removed features.Fixedfor bug fixes.Securityfor vulnerabilities.
Usually the right type is clear. Three of them cause the most questions:
Fixed: the behavior was wrong, and is now correct.Changed: the behavior worked as intended, and now works differently.Security: the change addresses a vulnerability. It could fit under Fixed or Changed, but its urgency and audience are different.
When you are unsure, ask whether the old behavior was a bug. If it was, use Fixed. If it was intentional and you are changing it, use Changed.
When a Security entry has a CVE identifier, lead with it so readers and security tools can match the entry to the advisory:
- CVE-2024-12345: out-of-bounds read when parsing malformed input.
An entry like "Rewrote JSON parser; 3x faster on large files" fits better under Changed than Performance. You can add a category if you genuinely need one, but you rarely will: Improved and New are often the same as Changed and Added, and Internal or Housekeeping changes are rarely notable enough to list at all. Keeping to the six leaves every changelog readable the same way, and parseable by the same tools.
Two other type requests are common:
- Dependencies are not a type of change. A dependency update can be harmless, a fix, or breaking. If it matters to your users, describe its effect under the right type. If it does not, leave it out.
- Known issues are discovered, not changed. Note them on the affected version or in your issue tracker. When one is fixed, it goes under
Fixed.
Breaking changes
Mark breaking changes clearly. The version number already signals them (under Semantic Versioning, a major release is where they belong), but the number is easy to miss, so highlight them in the entry. Breaking changes usually go under Changed or Removed. Add a short **Breaking:** marker so they stand out, and keep them with the type of change they are:
- **Breaking:** parse() now returns a result object instead of raising.
Say what breaks. The word means little until readers know which interface you keep stable: a command line, a library API, a network protocol, a file format, or a configuration schema. State which one your versioning scheme covers.
A short upgrade note can sit in the entry itself, such as "rename the color option to theme." When the steps are substantial, link to them (a migration guide or the release notes) rather than spelling them out here. A long procedure buries what changed and turns a scannable record into a how-to: a different kind of document, for a narrower audience. Keep the **Breaking:** marker on the entry itself, within its type, rather than collecting breaks into a separate section, so anyone scanning Changed or Removed sees them in place.
Structuring a release
Keep an Unreleased section at the top to collect upcoming changes. It shows readers what to expect, and at release time you move its contents into a new version. Starting on a project that has no changelog? Begin here, recording notable changes from now on. Reconstructing past releases from your version history is also worthwhile if you want a fuller record; either is fine.
A version starts with its number and date, for example ## [1.0.0] - 2017-07-17. Use the YYYY-MM-DD format. It orders from the largest unit to the smallest, avoids the confusion of regional date formats, and is an ISO standard.
The square brackets around [1.0.0] make it a Markdown reference link. Resolve it once at the bottom of the file, pointing each version to a comparison with the one before it:
[Unreleased]: https://github.com/your/project/compare/v1.1.0...HEAD
[1.1.0]: https://github.com/your/project/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/your/project/releases/tag/v1.0.0
[Unreleased] compares the latest tag to HEAD (the current state of your code), so it always shows what has accrued since the last release, and the oldest version links to its tag, since there is nothing earlier to compare it with. Now every version is tied to its tag and links to the exact diff of what changed: the same association a hosted release page makes for you, kept in a file you own instead of a host's database. Any host exposes tag and comparison URLs, so the pattern works wherever your code lives, and the link stays out of the heading, so the changelog still reads cleanly.
When you cut a release, rename Unreleased to the new version in both the heading and its link, then add a fresh, empty Unreleased section pointing at HEAD.
A version may open with a short summary before the typed sections: a sentence or two on the theme of the release or a notable change. This is optional. Use it when a release is worth introducing, and skip it otherwise.
You do not have to use Semantic Versioning. Calendar versioning, a plain number, or a date all work; note which scheme you use so readers can read your version numbers. Some projects release continuously and have no version numbers. A changelog still helps: keep dated entries under Unreleased.
Curate, don't accumulate
Keeping a changelog is partly an act of restraint. A changelog records notable changes, which means some changes are not notable and do not belong in it. Deciding which is which takes judgment, and that judgment is human.
What should the file be named?
Name it CHANGELOG.md. Some projects use HISTORY, NEWS, or RELEASES, but a predictable name makes it easy to find. A changelog does not need to list every change. Version control already does that. It lists the notable ones.
What goes at the top of the file?
Open with a # Changelog heading and a short, fixed preamble that says what the file is and which conventions it follows:
# 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).
Stating the conventions you follow tells readers, and tools, what to expect. The first link declares the format; the second names your versioning scheme (Semantic Versioning here, but reference whichever you use). Pin the Keep a Changelog link to the version you follow, so it stays accurate as this page changes.
Is a changelog the same as release notes?
No, although they draw from the same material. A changelog is the complete, ongoing record: every notable change, across every version, kept in one file in the repository and written plainly for anyone. Release notes are an announcement for a single release: a curated selection of its headline changes, often with upgrade steps and a marketing voice, published when that version ships. The changelog is the source; release notes are drawn from it and shaped for the announcement. Keep the changelog as the record, and write the release notes from it rather than maintaining two.
This does not have to mean doing the work twice. At release time, the version's section in the changelog is already the draft: copy it into the release, and expand it only if the announcement wants more. Because every version sits under a predictable ## [x.y.z] heading, a small script can extract that section and create the release without anyone retyping it.
A host will offer to do this for you. See that offer for what it is. Their release systems attach notes to a tag, notify watchers, and collect build files; often even generating the notes (from merged pull requests or commit messages). But everything is stored in the host's database, not your repository. These releases don't travel with the repository and follow you to another host: the convenience is the hook. Meanwhile, your changelog goes where your code goes; what a host generates and keeps for you does not, and you lose it the day you leave.
You can treat a generated draft as a starting point, but keep CHANGELOG.md as the canonical record. You can then generate host-specific release posts from it. A changelog file brings the same information these systems present, except it reads offline. You still benefit from the host's reach (notifications, a visible page, attached downloads) without entirely depending on it to hold your project's history.
What makes a changelog worse?
A few habits make a changelog less useful.
Commit log diffs
Do not use a list of commits as a changelog. It is full of noise: merge commits, unclear messages, internal changes. A commit records a step in the source code. A changelog entry records a notable difference, often across several commits, written for the people who use the software.
A "git log" is the list of commits in a git repository. We mention git because it is the most common version control system, but this applies to any of them: a raw commit history is not a changelog.
Ignoring deprecations
When someone upgrades, it should be clear what will break. Announce a deprecation before you act on it: mark it Deprecated in one release, and only Removed in a later one, so anyone upgrading meets the warning before the change. Say which version will remove it, so they can plan. If you do nothing else, always record deprecations, removals, and breaking changes.
Inconsistent changes
A changelog that records only some changes can mislead as much as no changelog. Readers treat it as the full picture. Leave out trivial changes, but include every notable one. A changelog is only trustworthy if it is kept up to date consistently.
Changelogs, automation, and LLMs
A changelog that follows a consistent shape can be read by tools and by language models, because it was written for people first. There is no separate machine format, and there will not be one: this page is the format. A consistent changelog is easy to parse, a benefit of writing clearly, not a reason to write for machines.
Machines can also help write a changelog. A language model can draft one from a diff in seconds, which is useful as a starting point. But it is now easy to publish a fluent changelog that no one has read. The principle that started this project matters more as machines write more code: machines can draft, but humans curate.
A model can't decide what's notable for your readers, or say it plainly for them. If you use a tool for the first draft, give it the brief you would give a contributor: summarize notable, user-facing changes; do not paste a git log; sort each change into one of the six types; explain the reason in the text; mark breaking changes; and remove anything not worth reading. Then read the result before anyone else does.
The same caution applies to changelogs generated from commit messages. A convention such as Conventional Commits, with tools such as semantic-release, release-please, Changesets, and git-cliff, reads structured commits to choose the next version and draft a changelog. That can give you a starting point, but a commit and a changelog entry are written for different people, and one does not convert cleanly into the other.
Generating the entry from the commit assumes that every commit belongs in the changelog, and that the right entry is a reworded commit message. Usually it is neither: many commits do not matter to your readers, and the changes that do often span several commits and need to be described from the reader's point of view. Sorting commits by type shortens the list, but it does not make that shift. A generated changelog is raw material at best: a person still has to choose what is notable, group it, and write it for the reader.
Continuous integration can help, but keep it in a supporting role. Use it for mechanical tasks: move the Unreleased section into a dated version at release time, check that the file is formatted correctly, and optionally remind a contributor that a change may need an entry. Do not make a changelog edit a required check on every change. That teaches people to add a line to pass the check, which fills the changelog with noise. Let automation handle the mechanics, and leave the judgment to people.
Less common questions
What about yanked releases?
A yanked release is a version pulled because of a serious bug or security issue. List it; do not hide it. Mark it like this:
## [0.0.5] - 2014-12-13 [YANKED]
The brackets make the tag easy to notice and easy to parse.
Should you ever rewrite a changelog?
You can improve a changelog after a release. A project may forget an entry, or discover a breaking change it did not record. Fixing this is reasonable. When you do, consider noting the date you updated the entry, so readers notice the change.
What if the changelog gets too big?
A single file is usually fine, even a long one; many projects keep decades of history in one CHANGELOG.md. If it becomes hard to manage, you can move old history into separate files, but link the main file and the archives both ways, or readers will not find the older entries. Archive only versions old enough that they will not need editing, and do not delete old entries: someone may still upgrade from an old version.
What about monorepos?
It depends on whether the repository holds one product or many. Unrelated projects that share a repository each keep their own changelog. A single product made of many parts (say, a framework split into separate libraries) can keep a changelog per component, but should also keep one central changelog. Readers should not have to read a dozen component changelogs to understand what a release means. The per-component changelogs are the detailed record; the central one is the summary.
Should I link to issues or pull requests?
You can, and it is sometimes helpful. Keep two things in mind: links break when a repository moves, and pull request numbers belong to one host, not to your code. Git tags and commit references stay with the repository. Link when it helps, prefer plain prose over a list of bare (#1234) references, and use portable references when you want a pointer that will still work later. Collect these as reference-style links at the bottom of the file, the way the version comparisons are, so the prose stays readable and every pointer lives in one place you control.
Should you credit contributors?
The commit history already records who did what, so a changelog does not need to credit anyone. But naming contributors, especially in a notable release, is a common and generous way to recognize their work and encourage more of it. If you do, keep it brief, and remember that a @handle belongs to one host, so a name or a link to a profile travels better. For fuller credits, a CONTRIBUTORS or AUTHORS file keeps them in the repository without crowding the record of what changed.
About Keep a Changelog
Is there a standard changelog format?
Not a formal one. There are older conventions, such as the GNU changelog style guide and the GNU NEWS file, but they are limited. Keep a Changelog does not aim to be the one true standard. It aims to show that clear, consistent communication about changes is worth the effort. It started from good practices in open source and applies to any project that needs to communicate its changes.
What does it deliberately leave out?
A convention is also defined by what it leaves out. Some common requests are deliberate non-goals:
- No new change types: six are enough.
- No machine format, schema, or strict layout: this is the format.
- No tool or service to install: a convention should cost only attention.
- No dependence on a vendor: a changelog is a plain file in your repository.
None of this is fixed; it is open to discussion. But additions to a widely used convention deserve care.
How can I contribute?
Keep a Changelog is one carefully considered opinion with examples, not the only way to communicate changes. It has helped many projects, and it is still a work in progress. Each version came from discussion in the community. Please contribute or start a conversation if you have ideas or need help.
References
Keep a Changelog grew from good practices observed in open source, gathered into the convention this page describes, and demonstrated in its own changelog. Olivier Lacan discussed the motivation behind it on The Changelog podcast.
Since then it has been translated into dozens of languages and adopted by tens of thousands of open-source projects whose changelogs note that their format is based on it.
Among the adopters are NASA and the UK's National Archives, the Wikimedia Foundation, and companies such as Cloudflare and Unity, several of which recommend it in their own contributor guides and engineering handbooks.
Its reach extends past software, too. Peer-reviewed research on software versioning and breaking changes cites it, and research-data guidelines such as The Turing Way and the Helmholtz Metadata Collaboration recommend it for tracking changes to scientific datasets.