Automating Changelogs with Changesets
Use Changesets to record intent-to-release, bump versions, generate changelogs, and publish dual-format packages from a monorepo or single package.
Maintaining a changelog by hand means someone has to remember, at release time, everything that changed since the last version — which pull request was a breaking change, which was a bugfix, and what to call each one in user-facing terms. That information is freshest at the moment a pull request is written, not weeks later when a release is finally cut. Changesets solves this by capturing intent-to-release as part of the same pull request that makes the change, as covered in Versioning & Changelog Automation, then mechanically turning accumulated changesets into a version bump and a CHANGELOG.md entry.
Root Cause
The core problem Changesets addresses is that semver bump decisions and changelog authorship are naturally distributed across many contributors and many pull requests, but version numbers and changelogs are inherently singular — one number, one file, updated at release time. Without tooling, this forces either a manual reconciliation step (error-prone, easy to skip) or blind trust in commit message conventions (fragile, hard to enforce for occasional contributors). Changesets decouples the two: each pull request writes a small, isolated markdown file describing its own change and bump type, and a separate changeset version command later aggregates every pending file into one coherent release, computing the correct combined semver bump and generating CHANGELOG.md entries automatically.
Minimal Reproduction
A repository with two unreleased changesets, both touching the same package:
<!-- .changeset/soft-lions-jump.md -->
---
"@acme/sdk": minor
---
Add a new `parseConfig` utility export
<!-- .changeset/tall-cats-jam.md -->
---
"@acme/sdk": patch
---
Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
Running the version command consumes both files at once and computes the highest bump type present (minor beats patch):
$ npx changeset version
--- a/package.json
+++ b/package.json
@@
- "version": "2.0.0",
+ "version": "2.1.0",
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@
+## 2.1.0
+
+### Minor Changes
+
+- Add a new `parseConfig` utility export
+
+### Patch Changes
+
+- Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
+
## 2.0.0
Both .changeset/*.md files are deleted as part of this run — they’ve been “consumed” into the release.
From Changeset File to Published Release
Step-by-Step Fix
Step 1 — Install and initialize Changesets
npm install --save-dev @changesets/cli
npx changeset init
This creates .changeset/config.json and a .changeset/README.md explaining the workflow to contributors.
Step 2 — Add a changeset per meaningful pull request
npx changeset
Answer the interactive prompts to select the affected package(s), the bump type, and a plain-language summary. Commit the resulting file in .changeset/ alongside your code change.
HAZARD PREVENTION
Symptom: A “Version Packages” release PR bumps a package’s version even though nothing in that package actually changed.
Root cause: A changeset was accidentally scoped to the wrong package during the interactive prompt, or
updateInternalDependenciesin.changeset/config.jsonis set to a level that cascades bumps to unrelated workspace dependents.Fix: Review the generated
.changeset/*.mdfile’s frontmatter before committing — it lists exactly which package names will be bumped — and adjustupdateInternalDependenciesif cascading bumps are too aggressive for your dependency graph.
Step 3 — For monorepos, configure fixed or linked groups
If several packages must always ship the same version number (a common choice for a small monorepo with tightly coupled packages), use fixed:
{
"fixed": [["@acme/core", "@acme/react", "@acme/vue"]]
}
If packages should version independently but a bump in one should never leave a dependent behind at a lower relative bump, use linked instead:
{
"linked": [["@acme/core", "@acme/plugin-a", "@acme/plugin-b"]]
}
Step 4 — Customize the changelog generator
The default generator produces plain bullet points. To include GitHub commit and PR links automatically:
npm install --save-dev @changesets/changelog-github
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@
- "changelog": "@changesets/cli/changelog",
+ "changelog": ["@changesets/changelog-github", { "repo": "acme/sdk" }],
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@
## 2.1.0
### Minor Changes
-- Add a new `parseConfig` utility export
+- [#142](https://github.com/acme/sdk/pull/142) [`a1b2c3d`](https://github.com/acme/sdk/commit/a1b2c3d) Thanks [@contributor](https://github.com/contributor)! - Add a new `parseConfig` utility export
Verification
Confirm pending changesets are detected correctly before merging a release branch:
npx changeset status --since=main
Expected output when releasable changes are pending:
🦋 info @acme/sdk: minor
🦋 This Changeset release will release 1 package
🦋 info npm info @acme/sdk
Then verify the version command produces the expected bump without actually publishing:
npx changeset version --dry-run 2>&1 || npx changeset version
git diff package.json CHANGELOG.md
Edge Cases / Gotchas
- Empty changesets for non-code changes. Use
npx changeset add --emptyto acknowledge a pull request intentionally has no release-worthy change, satisfying a CI check that requires a changeset file without triggering a version bump. - Pre-release branches. Changesets has a distinct pre-release mode (
npx changeset pre enter <tag>) — do not attempt to publish beta versions through the normalchangeset version/changeset publishflow without entering pre mode first; see Managing Prerelease and dist-tags on npm. - Workspace protocol dependencies. In a pnpm or Yarn workspace, internal dependencies declared as
workspace:*are rewritten to real semver ranges automatically duringchangeset publish— do not hand-edit these before publishing. - CI checkout depth.
changeset status --since=mainneeds full git history to diff against the base branch; a shallow checkout (actions/checkoutdefault) can report false negatives. Setfetch-depth: 0.
Frequently Asked Questions
What happens if two pull requests add changesets for the same package at the same time?
Nothing conflicts. Each changeset is its own uniquely-named markdown file, so two pull requests can merge independently without a merge conflict. The next changeset version run consumes all pending files together and computes a single combined version bump.
Can I write the changelog entry myself instead of using the auto-generated summary?
Yes. The text in the changeset markdown file body is exactly what appears in CHANGELOG.md, so edit it directly to be user-facing rather than accepting the first draft you typed during npx changeset.
How do fixed and linked package groups differ in a monorepo?
fixed forces every package in the group to share the exact same version number, bumping all of them together even if only one changed. linked keeps independent version numbers but ensures that if one package gets a bump, dependents in the same linked group receive at least that bump level too.
Does Changesets support custom changelog formatting?
Yes, via the changelog key in .changeset/config.json. The default @changesets/cli/changelog generator produces plain bullet points; swap in @changesets/changelog-github to get commit and PR links, or write a custom generator module for entirely bespoke formatting.
Related
- Versioning & Changelog Automation — the full workflow this guide is one part of, including CI automation with the changesets/action.
- Managing Prerelease and dist-tags on npm — how Changesets’ pre-release mode interacts with npm dist-tags for beta and canary builds.
- Publishing Packages from a pnpm Workspace — how fixed and linked package groups interact with workspace protocol dependencies in a multi-package repository.