Why Changelog Trust Is Earned, Not Assumed
A changelog is a promise to the developers who depend on your API, library, or service: "here's what changed, and here's what it means for you." When that promise is kept — when the changelog accurately describes breaking changes, when the format is consistent, when the entries are human-readable — the changelog becomes a primary navigation tool for upgrade decisions and integration planning.
When the promise is broken — when the changelog lists commit hashes and abbreviated messages, when breaking changes are buried in the middle of a minor release without clear signaling, when versions are listed without dates — developers learn not to trust it. They either read the source diff instead of the changelog, or they avoid upgrades until forced by a security issue.
The difference between a trusted changelog and an ignored one is almost entirely a question of discipline: the same information is captured in the git history either way. The discipline is choosing to present that information in a form that serves the reader rather than the author.
The Keep a Changelog Standard
The most widely adopted format for human-readable changelogs is the "Keep a Changelog" standard, maintained by Olivier Lacan at keepachangelog.com. The format has specific principles:
1. Written for humans, not machines. A changelog is not a git log. "Fix auth bug" is a git commit message; "Fixed authentication failure when token contains special characters (apostrophes, ampersands) in the username field" is a changelog entry.
2. Grouped by version. Each version is a section; within each section, changes are grouped by type.
3. Grouped by change type. The standard categories:
- Added — new features or capabilities
- Changed — changes to existing functionality
- Deprecated — features that will be removed in a future version
- Removed — features removed in this version
- Fixed — bug fixes
- Security — security-relevant fixes (especially important to call out explicitly)
4. Unreleased section. An [Unreleased] section at the top captures changes that are merged but not yet released. This gives contributors a place to add changelog entries as they merge PRs, rather than writing the entire changelog at release time.
5. Latest version first. Newest to oldest; the most immediately relevant information is at the top.
6. Semantic Versioning. Version numbers follow SemVer: MAJOR.MINOR.PATCH. MAJOR version increments signal breaking changes; MINOR signal new backward-compatible functionality; PATCH signal backward-compatible bug fixes. The changelog format and versioning scheme work together.
The CHANGELOG.md Format
# 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/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New `retry` option on the client constructor; configures automatic retry
behavior for transient errors (default: 3 retries with exponential backoff)
### Changed
- Response objects now include a `requestId` field; useful for support
ticket correlation
---
## [2.4.1] — 2026-10-20
### Fixed
- Authentication failure when username contains URL-encoded characters
(e.g., `user+name@example.com`); the `+` was being double-decoded
- Memory leak in WebSocket connection handler on long-lived connections
(> 6 hours); affected versions 2.3.0 – 2.4.0
### Security
- Updated `jsonwebtoken` dependency from 8.5.1 to 9.0.0; mitigates
CVE-2022-23529 (if not already addressed by prior update)
---
## [2.4.0] — 2026-10-05
### Added
- `client.search(query, options)` method; supports full-text search with
optional filter by date range and category
- Pagination support on `client.list()`; new `cursor` parameter replaces
deprecated `page` parameter
### Changed
- `client.get(id)` now returns 404 for soft-deleted records instead of
an empty object; previously, a soft-deleted record returned `{}` which
was ambiguous
### Deprecated
- `page` parameter on `client.list()` — will be removed in v3.0.0;
use `cursor` parameter instead. See migration guide.
---
## [2.3.0] — 2026-09-12
### Added
- Rate limit headers in all responses: `X-RateLimit-Remaining` and
`X-RateLimit-Reset`
### Fixed
- `client.delete()` was returning 200 instead of 204 on successful deletion
---
[Unreleased]: https://github.com/example/client/compare/v2.4.1...HEAD
[2.4.1]: https://github.com/example/client/compare/v2.4.0...v2.4.1
[2.4.0]: https://github.com/example/client/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/example/client/releases/tag/v2.3.0
The footer links map version identifiers to GitHub comparison URLs, making it easy to see exactly what changed between any two versions.
What Makes a Changelog Entry Useful
The entry answers: what changed, and what does it mean for me?
A developer reading the changelog is making a decision: should I upgrade? Is this version safe to upgrade to without breaking my integration? Does this version contain the fix or feature I was waiting for?
An entry that answers this question gives the developer enough information to make the decision without reading the source code or the PR description.
Useful entry structure for a bug fix:
Fixed authentication failure when username contains URL-encoded characters
(e.g., `user+name@example.com`); the `+` was being double-decoded
This entry:
- States what was fixed (authentication failure)
- States the specific condition (URL-encoded characters)
- Gives a concrete example (
user+name@example.com)
- States the root cause (double-decoding)
A developer who has this bug knows this entry applies to them. A developer who doesn't have this bug can quickly determine it doesn't affect them.
Useful entry structure for a breaking change:
[BREAKING] `client.get(id)` now returns 404 for soft-deleted records
instead of an empty object. If your code checks `if (result === {})`
to detect deleted records, update it to catch 404 errors instead.
See migration guide at [link].
This entry:
- Signals breaking change clearly (in square brackets, at the start)
- States what changed (404 instead of empty object)
- States the impact (if you do X, update to do Y)
- Links to migration guidance for complex transitions
What to exclude:
- Commit hashes or PR numbers without context
- Implementation details that don't affect consumers ("refactored auth middleware to use dependency injection")
- Developer experience changes that don't affect the API contract ("updated linting rules")
- Changes that are reverted in the same version
The Breaking Change Convention
Breaking changes deserve special treatment because they require action from every consumer of the API or library. The consumer cannot upgrade without reading the changelog; the changelog must give them enough information to plan and execute the migration.
Signal breaking changes explicitly: The [BREAKING] prefix in the entry title is visible when scanning. A breaking change buried in the Changed section without clear signaling is a trust violation — the developer upgrades and their integration breaks without warning.
For significant breaking changes: Include a migration guide, either inline in the changelog entry or linked from it. The migration guide covers: what the old behavior was, what the new behavior is, and what the consumer needs to change.
Version the breaking change correctly: A breaking change in a SemVer project increments the MAJOR version. v3.0.0 signals to consumers that this release may require migration work. v2.4.1 signals that it's safe to upgrade without reading the changelog for breaking changes.
The Deprecation Pipeline
Well-managed deprecations respect the time constraints of consumers who depend on your API. The standard deprecation pipeline:
Deprecation notice: In a MINOR version, mark the feature as deprecated with a specific removal timeline: "will be removed in v3.0.0" or "deprecated as of v2.4.0, will be removed in the next major version."
Warning in the code: Add a deprecation warning (a log message or a warning header) that fires when the deprecated feature is used. This surfaces the deprecation to developers who don't read changelogs.
Documentation update: Mark the deprecated feature in the API documentation. Include the migration path.
Removal: In the next MAJOR version, remove the deprecated feature. Include a Removed entry in the changelog that references the version where it was deprecated.
The deprecation pipeline allows consumers to migrate on their own schedule between the deprecation notice and the removal. The timeline should be long enough to be practical (at least one full release cycle; typically 3-6 months or one major version).
Keeping the Changelog Current: The Unreleased Section
The most common changelog problem: the changelog is updated once at release time, by whoever is managing the release, from the git log. This produces a changelog that reflects the git history rather than the consumer's perspective.
The solution: add changelog entries as part of the PR process, not at release time.
The workflow:
- Every PR that changes user-facing behavior adds an entry to the
[Unreleased] section of the changelog, in the correct category (Added, Changed, Fixed, Security, Deprecated)
- The PR checklist item: "Does this change the public API or user-visible behavior? If yes, is a changelog entry added?"
- At release time, the
[Unreleased] section is renamed to the version number and date; a new empty [Unreleased] section is created at the top
This approach distributes the changelog writing to the engineers making the changes, at the moment when they have the most context about what changed and why. It produces better changelog entries and removes the burden from the release manager.
Tools
Conventional Commits + Changelog generators:
If your team uses the Conventional Commits standard for commit messages (feat:, fix:, BREAKING CHANGE:, etc.), tools like conventional-changelog, semantic-release, or release-please (GitHub Actions) can generate draft changelogs automatically from commit messages. These tools are useful starting points; the generated entries often need human editing before publication.
GitHub Releases:
GitHub's Releases feature supports a release body (changelog entry for the release) and can auto-generate entries from merged PRs. GitHub's auto-generation is commit-message quality — useful for seeing what was merged but not consumer-ready without editing.
Manual CHANGELOG.md:
For most teams, a manually maintained CHANGELOG.md in the repository, updated via the PR process described above, is more reliable and more readable than auto-generated changelogs. Auto-generation is useful as a starting draft, not as the final product.
Worked Example: A v3.0.0 Breaking Change Release
A library team is releasing v3.0.0 with three breaking changes. The changelog entry:
## [3.0.0] — 2026-11-01
This is a major version with breaking changes. See the [v3 migration guide](MIGRATION_v3.md)
for detailed instructions.
### Removed
- [BREAKING] `client.list(options.page)` pagination parameter removed;
deprecated in v2.4.0. Use `options.cursor` instead.
[Migration: replace `page: N` with `cursor: prevResponse.nextCursor`]
- [BREAKING] `client.authenticate({ legacy: true })` option removed;
deprecated in v2.1.0. Legacy auth was OAuth 1.0a; all consumers must
use OAuth 2.0. [Migration guide section 3.1]
### Changed
- [BREAKING] `client.delete(id)` now returns `void` (no response body)
instead of returning the deleted object. Update code that reads
the return value of `delete()` calls.
[Migration: save the object before deleting if you need it afterward]
### Added
- New `client.batch(operations)` method for atomic multi-operation requests;
replaces the pattern of sequential individual operations
- TypeScript types now ship with the package; remove `@types/example-client`
from your dependencies if installed
### Fixed
- Exponential backoff on retry now correctly resets after a successful request;
previously, a success after retries kept the elevated delay for subsequent requests
This entry names the breaking changes explicitly, gives the specific migration action inline, links to a detailed migration guide, and covers both the removals (things to stop doing) and the additions (things to start doing).
Key Takeaways
- Write for the reader, not for the author: changelog entries answer "what changed and what does it mean for me?" — not "what did I implement?" — so developers can make upgrade decisions without reading source diffs.
- Use the Unreleased section and add entries as part of the PR process: changelog entries are most accurate when written by the engineer making the change, with the context fresh, not reconstructed from git log at release time.
- Signal breaking changes explicitly:
[BREAKING] at the start of an entry is visible when scanning; a breaking change that reads like a normal change is a trust violation.
- Deprecation pipeline gives consumers time to migrate: deprecation notice in one MINOR version → removal in the next MAJOR version; include removal timeline and migration path.
- Pair the changelog with SemVer: MAJOR version increments signal breaking changes; MINOR signals backward-compatible additions; PATCH signals backward-compatible bug fixes — the version number tells consumers whether to read the changelog carefully before upgrading.
Conclusion
A changelog that developers trust is accurate, human-readable, consistent in format, and explicit about breaking changes and their migration paths. The practice that produces trusted changelogs — adding entries as part of the PR process when the change is fresh, not reconstructed from git log at release time — is also the practice that produces better entries, because the engineers who made the changes write them. The format (Keep a Changelog), the workflow (Unreleased section, PR checklist), and the convention (SemVer, [BREAKING] prefix) together create a changelog that serves its actual purpose: letting developers make informed upgrade decisions without reading source code.
Try WebSnips free — save changelog best practices, API documentation references, and SemVer guidelines with your own annotations, tag by project and version policy, and build the organized technical standards library that keeps your release process consistent.