How to keep a changelog for an npm package
npm installs your package; it doesn't tell anyone what changed. That job falls to your CHANGELOG.md, your GitHub releases and two registry features most maintainers underuse: dist-tags and deprecation messages.
Updated Last checked 5 min readOpen-source libraries and packages
The short answer
- Keep CHANGELOG.md in the repository and link it, or your GitHub releases, from the README: the README is what the npm package page shows.
npm versionupdates package.json and, in a git repository, creates a version commit and tag. Use the same version in the changelog heading.- Publish prereleases with
npm publish --tag nextsolatestdoesn't move. - Use
npm deprecateto warn people away from broken or unsupported versions, and record it in the changelog. - Changes to
engines,peerDependencies,exports, module format and types are often the real breaking changes.
Changelog entry for an npm package
A major release with the three breaking changes npm packages ship most often, in Keep a Changelog 2.0.0 format: each one marked **Breaking:** under its type. Delete the ones that don't apply.
## [3.0.0] - YYYY-MM-DD
Three breaking changes: Node.js 20 or later, ESM only, and imports limited
to the paths in `exports`.
### Added
- `my-lib/utils` entry point.
### Changed
- **Breaking:** Node.js 20 or later is required (`engines.node` is now `>=20`).
- **Breaking:** The package is ESM-only. If you load it with `require("my-lib")`,
switch to `import { parse } from "my-lib"`, or stay on 2.x.
- **Breaking:** Only paths listed in `exports` can be imported. Deep imports such as
`my-lib/dist/utils.js` now fail with ERR_PACKAGE_PATH_NOT_EXPORTED.
Import from `my-lib/utils` instead.
### Deprecated
- `parseSync()`: use `parse()`. It will be removed in 4.0.0.
[3.0.0]: https://github.com/OWNER/REPO/compare/v2.4.1...v3.0.0my-lib, its functions and the versions are placeholders.
Free tool · no sign-up
Can a coding agent read your changelog?
Paste your repository, a GitHub release or a CHANGELOG.md link. The free converter shows every version, breaking change, migration step and deprecation it can extract, as JSON plus an llms.txt summary.
No AI and nothing stored. Or paste a CHANGELOG.md instead
01Put the changelog where npm users will find it
The npm package page shows your README, not your changelog. Add a ‘Changelog’ link near the top of the README that points at CHANGELOG.md or your GitHub releases, and set the repository field in package.json so people can get from npm to your code in one click.
Keeping CHANGELOG.md in the repository means it's versioned with the code and reviewed in pull requests. Publishing each entry as a GitHub release as well notifies people who watch the repository. Keep a Changelog vs GitHub Releases explains how to do both without double work.
02Make versions, tags and headings agree
npm version patch, minor or major updates package.json and, in a git repository, creates a version commit and a v-prefixed tag. It refuses to run if the working tree has uncommitted changes, so commit the changelog entry first. Use the same version in the changelog heading and the GitHub release title so readers, and tools, can match them up.
# 1. Move the Unreleased notes under "## [2.5.0] - YYYY-MM-DD" and commit
git commit -am "docs: changelog for 2.5.0"
# 2. Bump package.json, commit and tag (needs a clean working tree)
npm version minor # 2.4.1 -> 2.5.0, creates the tag v2.5.0
# 3. Publish and push the commit and tag
npm publish
git push --follow-tagsIn a monorepo, Changesets does this per package: changeset version bumps versions and writes each package's CHANGELOG.md, and changeset publish runs npm publish for every package with a new version.
03Publish prereleases without moving latest
By default npm publish tags the new version latest, which is what a plain npm install my-lib installs. Publish betas and release candidates under another dist-tag so nobody gets them by accident, and give each one its own changelog entry with the full prerelease version.
# package.json "version": "3.0.0-beta.1"
npm publish --tag next
# People opt in explicitly
npm install my-lib@next
# When 3.0.0 is stable, publish it as usual and it becomes latest
npm publish04Deprecate versions in the registry, not just in prose
npm deprecate attaches a message to a version or a range, and npm shows it as a warning to anyone who installs a matching version. Use it for versions with a serious bug, or majors you no longer support, and note it in your changelog.
# Warn anyone installing an affected range
npm deprecate my-lib@"< 2.3.1" "Security fix in 2.3.1, please upgrade"
# Undo it: an empty message un-deprecates
npm deprecate my-lib@2.3.0 ""For a deprecated function inside a version you still support, warn at runtime as well. Node's util.deprecate() wraps a function so it emits a deprecation warning the first time it's called. Then write the notice: how to write deprecation notices.
05Know what breaks npm users
Many breaking changes in npm packages live in package.json rather than in your code.
| Change | Who it breaks | Changelog line |
|---|---|---|
engines.node raised | Projects on older Node.js versions: npm warns, and refuses with engine-strict set | Requires Node.js 20 or later. |
peerDependencies narrowed | Apps outside the new range: npm reports a peer-dependency conflict | Requires react ^18 or ^19. |
| ESM-only build | CommonJS code that loads the package with require() | ESM-only: use import. |
exports added or narrowed | Anyone importing a path that isn't listed (ERR_PACKAGE_PATH_NOT_EXPORTED) | Only my-lib and my-lib/utils can be imported. |
| Type definitions changed | TypeScript users, even when runtime behavior is the same | Options.timeout is now number (was number | string). |
Under Semantic Versioning these belong in a major version, even when no function signature changed. More in semantic versioning and changelogs, and for how these changes look from your users' side, how to track breaking changes in npm dependencies.
Before and after
Too vague
3.0.0: modernized the build.
Specific and actionable
3.0.0 is ESM-only and requires Node.js 20+. Replace require("my-lib") with import. Deep imports like my-lib/dist/utils.js are no longer exported; import from my-lib/utils instead.
Fictional example: use your own verified names, versions and behavior.
LogPulse
Generate release notes with LogPulse
LogPulse is a hosted changelog that people and coding agents can both read. Here's exactly what it can do for the steps above, and what it can't.
Check your changelog, free
Paste a CHANGELOG.md or a GitHub releases link. A deterministic parser (no AI, nothing stored) shows the versions, breaking changes, migration steps and deprecations an agent can extract.
Draft notes from GitHub
Connect a repository: merged pull requests, commits to your default branch and published GitHub releases collect in an inbox. AI drafts a note in the tone you pick, listing breaking changes and deprecations only when the changes clearly show them. Nothing is published until you approve it. 3 AI drafts on Free; unlimited on Open source and Pro. Pro opens soon: .
Publish once, read everywhere
Approved notes go to a hosted changelog and an in-app widget, to email subscribers on Open source and Pro (Pro opens soon), and to a JSON feed and MCP server coding agents can query. CI and agents can publish with an API key.
What it doesn't do: LogPulse doesn't tag releases, bump versions, publish to npm or PyPI, or write CHANGELOG.md back to your repository; keep your release tooling for those. AI drafts can miss context, so review them like a pull request.
Before you publish
- The README links to the changelog
- The repository field is set in package.json
- The changelog heading, git tag and npm version match
- Prereleases are published with --tag
- Broken or unsupported versions are flagged with npm deprecate
- engines, peerDependencies, exports and type changes are listed as breaking where they are
Common questions
Does npm show my CHANGELOG.md?
Not on the package page; it shows the README. Link the changelog from the README. Whether CHANGELOG.md is inside the published package depends on your files field and .npmignore; check with npm pack --dry-run.
Should I unpublish a broken version?
Usually not. Deprecate it and publish a fix. npm restricts unpublishing, and removing a version can break installs that pinned it, while a deprecation warns new installs without breaking existing lockfiles.
How do I list a prerelease in the changelog?
Use the full prerelease version in the heading, for example ## [3.0.0-beta.1] - YYYY-MM-DD, and mark the GitHub release as a pre-release. When 3.0.0 ships, its entry can summarise everything since 2.x so stable users don't have to read every beta.
Are TypeScript type changes breaking?
If code that compiled before fails to compile after the upgrade, treat it as breaking for TypeScript users and list it, even if the JavaScript behaves the same.
Sources
Facts last checked against these sources on .
Read next
How to keep a changelog for a Python package on PyPI
Link a changelog from pyproject.toml, version with PEP 440, ship pre-releases pip skips, yank instead of delete, warn before removals, automate with towncrier.
Read the guide Open-source libraries and packagesHow to write release notes for open-source libraries
How to write release notes: a copy-paste template, 3 examples (library, CLI, breaking change), a 7-point checklist and how Django and Node.js do it.
Read the guide Breaking changes, deprecations and versioningHow to write deprecation notices
Say what is deprecated, what replaces it and when it will be removed, without inventing deadlines. A before/after deprecation notice and a 5-point checklist.
Read the guide Breaking changes, deprecations and versioningSemantic versioning cheat sheet for changelogs
Which change needs a major, minor or patch bump? A cheat sheet of 12 common changes with the changelog line for each, plus 0.x and prerelease rules.
Read the guide Breaking changes, deprecations and versioningHow to track breaking changes in your npm dependencies
Spot breaking changes before they break your build: read npm outdated, know what caret ranges allow, keep majors in their own Renovate or Dependabot PRs.
Read the guideMore guides: open-source libraries and packages
- Changelog vs release notes: what's the difference?
- CHANGELOG.md format, explained line by line
- Release notes templates in Markdown
- Keep a Changelog vs GitHub Releases
- How to generate a changelog from git commits
- How to write release notes from commits (Conventional Commits)
- semantic-release vs release-please vs Changesets (and git-cliff)
Tools and resources
- Show release notes inside a React or Next.js app
- semantic-release vs release-please vs Changesets (and git-cliff)
- Free tool: convert a CHANGELOG.md or GitHub release to JSON
- Free GitHub Action: release notes from your CHANGELOG.md on every tag
- kalmwerk/release-notes-action on GitHub: the Action's code and releases
- LogPulse developer docs: feed, API and MCP server
LogPulse
Changelogs coding agents can upgrade from.
Publish versions, breaking changes, migration steps and deprecations once. People get a widget and hosted changelog; agents get a JSON feed and MCP server. Free for open source.