There's a 10-year-old document at keepachangelog.com that answers most questions about how to write release notes. Most developers have never read it.
This post is the gist — plus the practical habits that make it stick.
The core principle
A changelog is for humans, not machines.
Your git log is a machine-readable (and developer-readable) record of every change. Your changelog is something different: it's for the person deciding whether to upgrade a dependency, update a package, or trust that a bug they hit last month is finally fixed.
Different audiences. Different documents. Don't conflate them.
The format
The Keep a Changelog format has become a de facto standard for good reason — it's simple, scannable, and answers the key question ("should I upgrade?") in under 10 seconds.
## [1.4.0] - 2025-03-28
### Added
- Dark mode support across all views
### Fixed
- Login failing silently on Safari 16
- Session not persisting after password reset
### Deprecated
- Legacy XML export endpoint (will be removed in v2.0)
### Breaking
- `init()` now requires a config object instead of positional args
The sections:
- Added — new features your users can use
- Fixed — bugs that are now resolved
- Changed — behavior changes that don't break existing usage
- Breaking — changes that will break existing integrations
- Deprecated — things that still work but are going away
- Removed — things that are already gone
- Security — always call these out explicitly, even for minor patches
You won't need all of them every release. Pick what applies. Skip the rest.
The mistake most devs make
Writing changelog entries that describe what you did, not what changed for the user.
❌ Refactored auth module to use new token architecture
✅ Fixed: sessions no longer expire unexpectedly after 30 minutes
Both describe the same change. Only one is useful to someone reading your changelog.
This is the hardest mental shift, and it's worth internalizing: every entry should be written from the user's perspective. What did they experience before? What do they experience now?
The Unreleased section
One of the most underused features of this format:
## [Unreleased]
### Added
- Real-time notifications (in progress)
### Fixed
- Avatar not loading in dark mode
Keep this section at the top of your CHANGELOG.md. Add entries to it as you merge pull requests or commit changes. When you cut a release, rename it to the version number and date.
This way your changelog is always in sync with your work, and writing it never feels like a big catch-up task.
When to write the entry
The answer most developers resist: at the same time you write the commit.
Not after the release. Not during the release prep. During the commit, while the context is fresh.
It takes 30 seconds. The alternative is staring at git log a week later trying to reconstruct what you actually changed for the user.
Tools that help
If your history is already messy — commits like fix, wip, final, ok this time — or if you just want to reduce the manual work, AI tools can generate a changelog draft from your git history.
I built WhatShipped specifically for this: connect your GitHub, GitLab, or Bitbucket repo and get a formatted changelog in about a minute. There's 1 free generation to try it without signing up for anything.
The bottom line
A changelog isn't paperwork. It's the cheapest trust signal a maintained project can send.
Users who can see what changed between versions are more likely to upgrade. They open fewer "is this fixed?" issues. They recommend your project to others.
The format is simple. The habit is what takes work. Start with the next release.
Top comments (0)