The Keep a Changelog format, and when to break from it
If you maintain a library, a CLI, or anything other developers pin to a
version, there is already a standard for your changelog. It's called
Keep a Changelog, written by Olivier
Lacan, and it has quietly become the default convention for
CHANGELOG.md files across open source. It deserves that position. It
also carries an assumption worth noticing before you apply it to a
customer-facing product changelog: it was written for developers
reading about versioned software. That assumption holds for a library.
It starts to bend for a SaaS product, and knowing where it bends tells
you when to follow the format and when to break from it.
What the standard actually says
Keep a Changelog is short. The whole convention fits on one page, which
is part of why it spread. A changelog, in its definition, is a curated,
chronologically ordered list of notable changes for each version of a
project, kept in a file (conventionally CHANGELOG.md).
Its guiding principles, roughly as the site states them:
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- Versions and sections should be linkable.
- The latest version comes first.
- The release date of each version is displayed.
- Say whether you follow Semantic Versioning.
It also recommends an Unreleased section at the top, where merged but
unshipped changes accumulate until the next release, and ISO dates
(2026-09-23 rather than formats that read differently in different
countries). And it is blunt about the main anti-pattern: a dump of
commit messages is a commit log, and a commit log makes a poor
changelog. Commits document what changed in the code. A changelog
documents what a person needs to know.
The six categories
The standard groups changes under six headings:
- Added for new features.
- Changed for changes to existing functionality.
- Deprecated for features that will be removed in a future release.
- Removed for features that are now gone.
- Fixed for bug fixes.
- Security for vulnerability-related changes.
Two of these do work that most homegrown category schemes skip.
Deprecated and Removed form a pipeline: a feature is announced as
going away, survives for a stated period, then its removal is recorded.
A developer who reads your changelog release by release never gets
surprised by a disappearance, because the warning and the removal are
separate, dated entries. And Security gives vulnerability fixes a
heading of their own, so anyone auditing whether they need to upgrade
can find them without reading everything else.
What the format gets right
The principles age well because they are mostly about the reader, not the file. "For humans, not machines" rules out the commit dump. Grouping by type of change lets a reader scan for the one category they care about. Latest first respects that almost everyone arrives asking "what changed recently?" Linkable versions mean a maintainer can answer a bug report with a URL instead of a paragraph. Displayed release dates let a reader line your changelog up against when their own problem started.
None of that is specific to open source. If you publish any changelog at all, those habits transfer directly.
The assumption underneath it
The standard's implied reader is a developer who consumes your software
at a specific version and chooses when to upgrade. Every structural
choice follows from that. Version numbers are the organising unit
because the reader has one installed. Semantic Versioning matters
because the reader uses the version number to judge upgrade risk. The
Deprecated category matters because the reader has integration code
that will break. The Unreleased section matters because the reader
might build from source or want to see what's coming in the next tag.
A customer of a SaaS product matches almost none of that. They are on whatever version you deployed this morning. They never chose to upgrade, can't decline to, and have no idea what your version numbers are. The standard was written for a different reader, and it shows the moment your audience changes.
Where a customer-facing changelog should diverge
If your changelog's readers are customers rather than developers pinning versions, break from the format in specific, deliberate ways.
Organise by date, not version number. "2.14.3" carries meaning for someone who runs 2.13 in production. It carries nothing for a customer of a continuously deployed product. Date-stamped entries, latest first, do the same organising job in units the reader actually experiences. The standard's own principle (display the release date) simply takes over as the primary key.
Translate the category labels. Added, Fixed, and Changed
survive contact with customers reasonably well, though many teams
prefer New / Improved / Fixed. Deprecated doesn't survive. It's
compiler vocabulary. If a capability is going away, a customer needs a
sentence with a date in it, not a heading: what stops working, when,
and what to use instead. The discipline behind the Deprecated category
(warn first, remove later, on the record) is worth keeping in full.
The label is worth dropping.
"Every version gets an entry" becomes "every noticeable change gets an entry." The standard's rule exists so no release ships undocumented, and it's a good rule. Without versions, the honest translation is a coverage rule: if a customer could notice the change, it appears; if they couldn't (refactors, dependency bumps, internal tooling), it doesn't. A dev-facing changelog reasonably records a build system migration. A customer-facing one that includes it is teaching readers to skim.
Give breaking changes more prominence than the format provides. In
the standard, a breaking change lives under Changed or Removed,
with the semver major bump carrying the warning. Customers don't read
version numbers, so the entry has to carry the weight alone: stated
plainly, visually distinct, with the deadline and the required action
in the entry itself.
Drop the Unreleased section. Accumulating unshipped changes at the top of the file is genuinely useful for contributors and for anyone tracking the next release. Shown to customers, it's a roadmap, and a changelog that previews unshipped work invites support tickets about features that don't exist yet. Publish what's live. Keep the rest internal.
Keep the spirit of Security, adjust the delivery. A heading that says "Security" on a public product page raises questions a library's audience wouldn't ask. Tell customers what they need to do (rotate a key, update an integration) plainly and promptly. Whether that belongs in the changelog or in a direct email depends on severity, which is a judgement the format can't make for you.
Run both, honestly
The clean resolution for a small product team is two changelogs with
two jobs. A CHANGELOG.md in the repo, following Keep a Changelog as
written, for the readers the standard was designed for: contributors,
integrators, future you. And a customer-facing changelog, organised by
date, in plain language, covering only what customers can observe. They
describe the same releases. They answer different questions, and
neither does the other's job well.
The failure mode to avoid is the untended middle: one document that half-follows the standard, addresses nobody in particular, and slowly goes quiet.
The second of those two changelogs is the one we build for.
ChangelogPilot reads your merged GitHub PRs, drafts customer-facing
entries, categorises them, and leaves the internal-only changes out of
the draft, so keeping the customer changelog current stops depending on
someone finding the time to write it. Your CHANGELOG.md can stay
exactly as Keep a Changelog intends. If the customer-facing half is the
one gathering dust, that's the problem we work on.