How to write release notes people actually read
Release notes belong to one release. Version 2.4 went out; this is the document that says what's in it, what it changes, and what the reader needs to do about it. That scoping is the whole difference between release notes and a rolling changelog, and we've written up the full distinction if the terms are blurring together. This piece is the craft side: how to write the notes for a release that's actually shipping, so that the people affected by it read them and act on them. It's the release-scoped counterpart to our guide on writing a changelog, which covers the continuous record.
Start from the upgrade decision
Someone reading release notes isn't browsing the way a changelog reader is; they're deciding whether to act. They run version 2.3 of your library, CLI, app, or self-hosted service, and they want to know whether taking 2.4 is safe, what it costs them, and when they should do it. Every choice below follows from serving that decision. If a sentence doesn't help someone upgrade with confidence, it's a candidate for cutting.
This also tells you when the format matters. If you deploy continuously and customers never install anything, there's no upgrade decision to serve, and your changelog is doing the job already. Write distinct release notes when there's a versioned artefact on the other end.
Put anything that needs action at the top
The natural instinct is to open with the new features and tuck the breaking changes somewhere near the bottom, because features are the good news. Resist it. The reader who skims past a buried breaking change finds out about it in production, and that's the last time they trust your notes.
Anything that requires action goes first: breaking changes, deprecations with removal dates, required migration steps, changed defaults. Each one states plainly what breaks, from when, and what to do.
Weak: "We've modernised our authentication flow."
Better: "API keys created before version 2.0 stop working in this release. Generate new keys from Settings before upgrading; the old keys keep working on 2.3 until 1 December. Migration guide: [link]."
Nobody has ever been annoyed that a breaking change was too clearly labelled.
Give the notes a stable shape
A structure that holds up for most software, top to bottom:
- A short summary. Two or three sentences on what this release is about. If someone reads nothing else, they should come away knowing whether it affects them.
- Breaking changes and required actions, as above.
- New features, each with what it does and where to find it.
- Changes to existing behaviour that aren't breaking but are noticeable.
- Fixes, written from the user's side.
- Known issues. What's still broken or incomplete in this release.
- Upgrade instructions, if upgrading takes more than clicking a button.
Keep the same shape release after release. A reader who took 2.3 and 2.4 should know exactly where to look in 2.5. And give each version's notes a permanent home — a versioned docs page, a GitHub release, a stable URL — because release notes get consulted months later, usually by someone debugging.
Write each entry from the reader's side
The line-level craft is the same discipline a good changelog entry needs: describe the effect on the reader, leave out the implementation, one change per entry. The difference in release notes is depth. Because the reader is about to act on this document, an entry can afford a second sentence of context that a changelog line would skip.
Weak: "Performance improvements and various bug fixes."
Better: "Imports of files over 50MB no longer time out. The importer now streams the file rather than loading it into memory, so a large import takes roughly as long as a small one."
The weak version is the single most common line in release notes, and it tells the reader nothing except that you didn't want to write the real list. If a fix mattered enough to ship, it matters enough to name.
Include the known issues
This is the section most teams omit and the one that builds the most trust. If the new export feature doesn't handle CSV yet, or the upgrade briefly logs everyone out, say so in the notes rather than letting support discover it one ticket at a time. A reader who sees an honest known-issues section believes the rest of the document more, because it's evidence the notes are a record rather than a promotion.
What to leave out
Internal refactors, dependency bumps, test coverage, build tooling — real work, but if the reader can't observe the result, it doesn't earn a line. The exception is when internal work changes something measurable, like startup time or memory use; then describe the measurable thing. The same goes for enthusiasm. "We're thrilled to announce" spends the reader's attention on your feelings instead of their upgrade. State what changed and let the release speak for itself.
Completeness cuts the other way, though. Within the release, every user-visible change belongs in the notes, including the awkward ones. Notes that quietly skip an inconvenient behaviour change stop being a document anyone can upgrade from.
If the part you dread is reconstructing what actually shipped, that's the problem ChangelogPilot works on. It drafts the record from your merged GitHub PRs, sorts the breaking changes and fixes into the shape above, and hands you an editing job instead of a blank page. You can connect a repo and watch it draft your next release from the work you've already merged. The guidance above holds either way, with any tool or none.