Nobody reads changelogs. Until something breaks.
Then suddenly your changelog is the most important document in the repository. Users need to know what changed, when, and why. Contributors need to understand the project's direction. And you — the maintainer — need a historical record that doesn't require archaeological dig through git log.
I maintain three open source projects. The one with a good changelog gets 70% fewer "what changed?" issues. Here's what I've learned about writing changelogs that actually serve their purpose.
What Makes a Good Changelog
The Keep a Changelog project nailed the definition: "A changelog is a file which contains a curated, chronologically ordered list of notable changes for each version of a project."
The key word is curated. A changelog is not a git log dump. It's a human-readable summary of what matters to users.
The Gold Standard: React's Changelog
React's changelog is widely regarded as one of the best in open source. Here's why:
## 18.3.0 (April 25, 2024)
### React
- Add ref cleanup function support
- Warn about deprecated string refs
- Deprecate `defaultProps` for function components
### React DOM
- Fix hydration error messages to include the component stack
- Support `fetchPriority` on images
Notice what they do well:
- Grouped by package (for monorepos)
- Action-oriented language — "Add," "Fix," "Deprecate"
- User-focused — describes what changes mean, not implementation details
- Concise — each item is one line
The Anti-Pattern: Git Log Dump
Here's what bad changelogs look like:
## v2.1.0
- 4a2f8bc fix typo in readme
- 8d1e3af update dependencies
- 2c9f1be refactor internal utils
- 7b3d4ea fix the thing
- 1e5a2cd more fixes
- 9f8c3ba WIP
- 3d2a1ef merge branch 'feature-x'
This tells users nothing. Internal commits, WIP messages, merge commits — none of this helps someone decide whether to upgrade.
The Changelog Structure That Works
After studying dozens of well-maintained projects, here's the structure I recommend:
# Changelog
## [Unreleased]
## [2.1.0] - 2026-03-20
### Added
- New CSV export option for reports (#142)
- Dark mode support for dashboard
### Changed
- Improved search performance by 40%
- Updated minimum Node.js version to 20
### Fixed
- Fixed date picker not working in Safari (#138)
- Fixed memory leak when processing large files
### Deprecated
- `legacyMode` option will be removed in v3.0
### Removed
- Dropped support for Node.js 16
### Security
- Updated `xmldom` dependency to fix CVE-2024-XXXX
The six categories (Added, Changed, Fixed, Deprecated, Removed, Security) cover every type of change a user needs to know about.
Writing Good Changelog Entries
Rule 1: Write for Users, Not Developers
Bad: "Refactored the query builder to use prepared statements"
Good: "Fixed SQL injection vulnerability in search feature"
Your users don't care about your implementation choices. They care about what's different for them.
Rule 2: Lead with the Action
Start every entry with a verb: Added, Fixed, Changed, Removed, Improved, Updated.
Bad: "The API now supports pagination"
Good: "Added pagination support to the API (default: 50 items per page)"
Rule 3: Include Issue/PR References
- Fixed file upload failing for files over 10MB (#234)
This lets users quickly find the full context if they need it.
Rule 4: Highlight Breaking Changes
Breaking changes should be impossible to miss:
### Changed
- **BREAKING**: Renamed `getUser()` to `fetchUser()` — see migration guide
- **BREAKING**: Minimum Node.js version is now 20 (was 16)
Automating Changelog Generation
Writing changelogs manually is tedious. Here's how to automate without losing quality.
Approach 1: Conventional Commits
If your team uses Conventional Commits, tools can auto-generate changelogs from commit messages:
feat: add CSV export option
fix: resolve Safari date picker issue (#138)
perf: improve search performance by 40%
BREAKING CHANGE: rename getUser() to fetchUser()
Tools like standard-version or semantic-release parse these prefixes and generate changelog entries automatically.
The downside: this only works if everyone on the team follows the convention consistently. In my experience, it takes about a month before it becomes habit.
Approach 2: PR-Based Changelogs
GitHub Actions can generate changelog entries from PR titles and labels:
- Label PRs with
feature,bugfix,breaking, etc. - On release, a workflow collects all merged PRs since the last tag
- Groups them by label and generates the changelog
This is how projects like Next.js and Tailwind do it.
Approach 3: AI-Assisted Generation
For projects where you haven't been following conventions from the start, AI can help bridge the gap. Feed your recent commits and diffs to an AI tool, and it can generate human-readable changelog entries.
Changelog Generator does exactly this — you point it at your git history, and it produces a structured changelog with proper categorization. It's particularly useful for catching up on changelog debt when you've been shipping features without documenting changes.
Approach 4: Hybrid
The most practical approach for most teams:
- Use conventional commits for structure
- Auto-generate a draft changelog on each release
- Have a human review and edit the draft before publishing
The automation handles the tedium; the human ensures quality.
Changelog Tips from Real Maintainers
I talked to several open source maintainers about their changelog practices:
"Write the changelog entry when you write the code." — Don't try to reconstruct what changed from a list of commits three weeks later. Add a changelog entry as part of each PR.
"Changelogs are documentation, not celebration." — Resist the urge to hype features. Users reading a changelog want information, not marketing.
"Version numbers are for computers; changelogs are for humans." — Your semver tells machines about compatibility. Your changelog tells humans what to expect.
"If you have to choose between detailed and up-to-date, choose up-to-date." — A brief, current changelog is infinitely more useful than a detailed, abandoned one.
Setting Up Changelog Automation in 10 Minutes
Here's the quickest path to automated changelogs:
# Install standard-version
npm install --save-dev standard-version
# Add to package.json scripts
"scripts": {
"release": "standard-version"
}
# First release
npx standard-version --first-release
# Subsequent releases
npm run release
This will:
- Bump the version based on commit types
- Generate/update CHANGELOG.md
- Create a git tag
The Changelog Checklist
Before publishing any release:
- [ ] All user-facing changes are documented
- [ ] Breaking changes are clearly marked
- [ ] Entries are written from the user's perspective
- [ ] Version number follows semver
- [ ] Date is included
- [ ] Issue/PR references are linked where relevant
- [ ] Security fixes are called out explicitly
Your changelog is a contract with your users. Keep it honest, keep it current, and they'll trust your project with their production code.
Need to generate a changelog from your git history? Try it free at changelog-generator-orcin.vercel.app.
Top comments (0)