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

Changeset lifecycle Vertical flow showing a contributor adding a changeset file, merging it to the main branch, a version command consuming pending changesets to bump the package version and write the changelog, and finally publishing the result to npm. Add changeset file Merge pull request changeset version Bump + changelog written changeset publish

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 updateInternalDependencies in .changeset/config.json is set to a level that cascades bumps to unrelated workspace dependents.

Fix: Review the generated .changeset/*.md file’s frontmatter before committing — it lists exactly which package names will be bumped — and adjust updateInternalDependencies if 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 --empty to 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 normal changeset version/changeset publish flow 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 during changeset publish — do not hand-edit these before publishing.
  • CI checkout depth. changeset status --since=main needs full git history to diff against the base branch; a shallow checkout (actions/checkout default) can report false negatives. Set fetch-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.



↑ Back to Versioning & Changelog Automation