Changesets lets contributors choose version bumps and write changelog entries in the same pull request as their code. This guide sets up Changesets v3 for a JavaScript monorepo, automates releases with changesets/action v2, and explains dependency updates and migration from v2.
Jump to: Setup · GitHub Actions · Monorepo configuration · Prereleases · v3 migration · Troubleshooting
What is Changesets?
Changesets is an open-source tool for managing package versions and changelogs in JavaScript repositories, built with a focus on monorepos. Contributors describe each change in a small Markdown file, a changeset, and the tool turns the accumulated files into version bumps, changelog entries, and npm releases. It’s unrelated to the general version-control meaning of “changeset” (a set of changes committed together, as in Git, Mercurial, Liquibase, or AWS CloudFormation); this post is only about the tool.
The word “changesets” means three things in the docs, and it helps to separate them:
- A changeset: a Markdown file in
.changeset/that describes one change. Its YAML front matter lists the affected packages and the bump for each; its body is the changelog entry. - The workflow: contributors add a changeset with their PR, changesets accumulate on
main, and a release consumes all of them at once. - The tool: the
@changesets/clipackage that writes changeset files, applies them (changeset version), and publishes (changeset publish).
A changeset looks like this:
---
"@acme/reports": minor
"@acme/core": patch
---
Add CSV export to the reports table. Exports respect the active filters and column order.
When you release, Changesets reads every pending file, works out the highest bump per package, bumps the versions, writes each package’s CHANGELOG.md, updates internal dependency ranges where needed, and deletes the consumed changeset files. The separate publish command publishes unpublished package versions to npm and creates Git tags.
The important difference from semantic-release and Release Please is the input. Those tools read your commit messages and infer the bump from feat: and fix:. Changesets doesn’t look at commit messages at all. The person making the change states the bump and writes the summary, deliberately, in the PR where reviewers can see and edit it.
How to set up Changesets, step by step
Step 1: Check the requirements
Changesets v3 needs Node.js ^22.11 || ^24 || >=26 and one of pnpm 10+, npm 10.9+, or Yarn 4.5.2+. Yarn Classic is no longer supported. The maintainers recommend pnpm for its security defaults and monorepo support, but all three work. Your repository needs workspaces configured (pnpm-workspace.yaml or the workspaces field in package.json) if it’s a monorepo; single-package repos need nothing extra. For npm workspaces, run npm install and commit the generated package-lock.json so the workspace can be detected consistently.
Step 2: Install and initialise
npm install -D @changesets/cli
npx @changesets/cli init
At a pnpm workspace root, use pnpm add -Dw @changesets/cli (omit -w in a single-package repository) and pnpm changeset init; with Yarn, yarn add -D @changesets/cli and yarn changeset init. The init command prompts for configuration and creates a .changeset/ folder containing a README.md and a config.json.
The rest of this post uses the npm form, npx @changesets/cli <command>. In a pnpm or Yarn project, pnpm changeset <command> and yarn changeset <command> are the equivalents, and the behaviour is identical.
Step 3: Review the configuration
Open .changeset/config.json. Most projects only need to look at three options:
{
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"fixed": [],
"linked": [],
"ignore": []
}
accessdefaults to"restricted", which publishes scoped packages as private. If your packages are public, set it to"public", or setpublishConfig.accessper package. This is the most common reason a first publish fails or ends up invisible.baseBranchdefaults tomainsince v3. Set it tomasterif that’s your default branch.changelogpicks the changelog generator. The default writes the commit hash and summary.@changesets/changelog-githubadds PR links and a “Thanks @author!” line, which is worth it for open-source projects:
{
"changelog": ["@changesets/changelog-github", { "repo": "acme/widgets" }]
}
Install it with npm install -D @changesets/changelog-github. In GitHub Actions the repo option is optional, since it defaults to the GITHUB_REPOSITORY variable. For a local versioning run with @changesets/changelog-github, also provide a repository-authorized GITHUB_TOKEN; the GitHub Action supplies it during CI versioning. Setting "changelog": false turns changelog generation off entirely, for repositories that only want the version bumps.
Step 4: Add your first changeset
In a feature branch, after making a change:
npx @changesets/cli
The CLI lists the packages that changed since baseBranch, asks which ones should be released, asks for the bump type of each, and asks for a summary. It writes a randomly named file like .changeset/brave-dolphins-sing.md. Commit it with the rest of the PR.
The summary is the part that matters most, because it goes into the changelog word for word. Write it for the person reading the changelog, not for the reviewer: what changed for them, and what they need to do if anything. You can edit the file in the PR like any other file, and reviewers can suggest changes to it.
For scripts and AI agents, v3 has non-interactive flags:
npx @changesets/cli add --minor @acme/reports --message "Add CSV export to the reports table"
Each bump flag takes a package name, even in a single-package repository. Use --patch @acme/core alongside --minor @acme/reports to include both packages, or pass comma-separated names for the same bump type.
Step 5: Version and publish by hand (once)
Before automating, run version locally and inspect the resulting diff. The publishing commands below perform a real npm release: use an npm account with access to the packages, and run them only when you are ready to ship. If your packages need compilation, build them after versioning so the published output uses the new versions.
npx @changesets/cli version # bumps versions, writes CHANGELOG.md, deletes the changesets
npm run build --if-present # use your workspace build command if different
git add . && git commit -m "Version packages"
npm login # authenticate for a local npm publish
npx @changesets/cli publish # publishes to npm and creates Git tags
git push --follow-tags
Tags are pkg-name@1.3.0 per package in a monorepo, or v1.3.0 in a single-package repo. publish only publishes versions that aren’t on the registry yet, so it’s safe to re-run after a partial failure.
Changesets CLI commands at a glance
| Command | What it does |
|---|---|
changeset init |
Creates .changeset/ with a config.json and README.md |
changeset or changeset add |
Writes a new changeset file; --major <pkg>, --minor <pkg>, --patch <pkg>, and --message <text> supply the release details |
changeset --empty |
Adds a changeset that releases nothing, for changes that need no release |
changeset status --since main |
Lists pending releases; exits 1 if packages changed without a changeset |
changeset version |
Applies pending changesets: bumps versions, writes changelogs, deletes the files |
changeset publish |
Publishes unpublished versions to npm and creates Git tags |
changeset pre enter <tag> / pre exit |
Enters and leaves prerelease mode |
changeset version --snapshot <tag> |
Produces throwaway snapshot versions for testing |
changeset git-tag |
Creates Git tags without publishing (was changeset tag in v2) |
Changesets GitHub Action: automating version and publish
The standard automation answers two questions, and Changesets’ docs treat them separately.
The changeset bot and PR checks
The changeset bot is the easiest option: install the GitHub App and it comments on every PR saying whether a changeset is present, with a link to add one from the browser. Alternatively, changesets/action/pr-status and changesets/action/pr-comment do the same from your own workflow.
The maintainers explicitly recommend against making a missing changeset fail CI, because not every change needs a release. If you want a strict rule anyway, this fails when packages changed without a new changeset:
npx @changesets/cli status --since main
Pair it with changeset --empty, which adds a changeset that releases nothing, for PRs that only touch tests or tooling.
The Version Packages pull request
The release automation runs on every push to main and does one of two things:
- If there are pending changesets, it runs
changeset versionand opens (or updates) a pull request called “Version Packages” containing the version bumps and changelog edits. - If there are none, because that PR was just merged, it publishes any versions that aren’t on npm yet, then pushes Git tags and creates GitHub Releases.
So the human step is merging the Version Packages PR, the same idea as Release Please’s release PR, with the difference that the changelog text came from people rather than commit messages.
Two settings have to be in place first. The workflow needs contents: write and pull-requests: write permissions, and in the repository’s Settings → Actions → General, “Allow GitHub Actions to create and approve pull requests” must be enabled. Without that you get GitHub Actions is not permitted to create or approve pull requests or Permission to … denied to github-actions[bot].
The workflow, with npm trusted publishing
npm’s recommended way to publish from CI is trusted publishing, which uses a short-lived OIDC credential instead of a stored token. Changesets’ docs recommend it too, with one deviation from npm’s own example: give id-token: write only to the job that publishes, which means splitting the work into jobs. This is the recommended v3 workflow, slightly condensed, at .github/workflows/release.yml:
name: Release
on:
push:
branches:
- main
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
select-mode:
runs-on: ubuntu-latest
outputs:
mode: ${{ steps.select-mode.outputs.mode }}
publish-plan-artifact-id: ${{ steps.select-mode.outputs.publish-plan-artifact-id }}
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm install
- id: select-mode
uses: changesets/action/select-mode@v2
version:
if: needs.select-mode.outputs.mode == 'version'
needs: select-mode
runs-on: ubuntu-latest
permissions:
contents: write # commit the version changes
pull-requests: write # open the Version Packages PR
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
package-manager-cache: false
- run: npm install
- uses: changesets/action/version@v2
pack:
if: needs.select-mode.outputs.mode == 'publish'
needs: select-mode
runs-on: ubuntu-latest
outputs:
pack-dir-artifact-id: ${{ steps.pack.outputs.pack-dir-artifact-id }}
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
package-manager-cache: false
- run: npm install
- run: npm run build
- id: pack
uses: changesets/action/pack@v2
with:
publish-plan-artifact-id: ${{ needs.select-mode.outputs.publish-plan-artifact-id }}
publish:
needs: pack
runs-on: ubuntu-latest
permissions:
contents: write # tags and GitHub Releases
id-token: write # npm trusted publishing
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 24
package-manager-cache: false
- run: npm install
- uses: changesets/action/publish@v2
with:
pack-dir-artifact-id: ${{ needs.pack.outputs.pack-dir-artifact-id }}
It’s longer than the one-step workflows you’ll find in older tutorials, and each piece earns its place:
select-modedecides whether this run should version or publish, with read-only permissions.versionrunschangeset versionand opens the PR. It can write to the repo but can’t publish.packbuilds and packs the tarballs with read-only permissions, so the project’s explicit build step runs without publishing permissions.publishpublishes the tarballs thatpackproduced. It’s the only job withid-token: write.permissions: {}at the top resets everything, so each job gets only what it declares, andpackage-manager-cache: falseavoids restoring a dependency cache that another workflow could have poisoned.
The publish job still runs npm install, which can execute dependency lifecycle scripts with that job’s permissions. This workflow separates the explicit build from publishing; it does not eliminate all script execution in the publish job. If your dependencies support installation without lifecycle scripts, consider a locked install with npm ci --ignore-scripts there and verify it in your own project.
For extra safety, the docs suggest putting the publish job in a GitHub environment with required reviewers, so a maintainer approves each publish run.
To set up trusted publishing on npmjs.com, add a Trusted Publisher in the package’s settings with your repository and this workflow’s filename. The Changesets docs note that a brand-new package must be published once by hand first (a stub package.json at version 0.0.0 is enough), after which you switch it to trusted publishing and add a changeset for the real first version.
For npm trusted publishing, check these details before the first automated publish:
- Use a GitHub-hosted runner and npm CLI 11.5.1 or newer. Check the installed version with
npm --version; a supported Changesets runtime alone does not establish npm compatibility. - Match the repository and workflow filename exactly. If the publish job uses a GitHub environment, configure the same environment name on npm.
- Ensure each package’s
repository.urlpoints to the GitHub repository. - Complete the first successful publish within two days of creating the trusted publisher configuration. An unvalidated configuration expires after that window and must be recreated.
The short version, for private repos
If you publish with a token, for example to a registry that doesn’t support trusted publishing, or you’re in a private repo with trusted contributors, the combined action still works in a single job:
Save this complete alternative as .github/workflows/release.yml instead of the multi-job workflow:
name: Release
on:
push:
branches: [main]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org/
package-manager-cache: false
- run: npm install
- run: npm run build
- uses: changesets/action@v2
with:
publish-script: npx @changesets/cli publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
The job has both repository-write permissions because it handles version PRs and publishing. For npm, the Changesets docs recommend trusted publishing over stored tokens. If you use a granular token, confirm its publishing permissions, expiry, and your package’s authentication policy against the current npm token documentation; other registries have their own requirements.
Changesets monorepo configuration
Monorepos are what Changesets was built for, and four options cover most needs.
Internal dependencies. A new version of @acme/core does not automatically mean every dependent needs a release. With the default behavior, @acme/reports can remain unchanged if its existing dependency range still accepts the new core version and it has no changeset of its own. If the new version falls outside that range, Changesets updates the range and adds a patch release for the dependent.
updateInternalDependencies controls range rewriting in packages that are being released; it does not mean “release every dependent.” With "patch" (the default), qualifying patch updates can raise the minimum dependency version. With "minor", an in-range patch can leave that minimum unchanged. An out-of-range update still requires a new range. See the configuration reference.
fixed makes a group of packages always release together at the same version, even if only one changed:
{ "fixed": [["@acme/button", "@acme/theme", "@acme/icons"]] }
linked makes a group share a version number only among the packages that are actually being released, without forcing the others to publish.
ignore temporarily excludes packages from releases, which is useful for merging work on a package that isn’t ready to ship. The docs stress that it’s for temporary use; to never publish a package, set "private": true in its package.json. Since v3, private packages aren’t versioned or tagged at all unless you set privatePackages.
One v3 change matters a lot here: a peer dependency update now bumps dependents by patch, not major. In v2, a minor release of a package forced a major release of every package that listed it as a peer dependency. If a dependent really is broken by the new peer version, add an explicit major for it in the changeset.
Worked example: when does a dependent package release?
We ran the following four cases with Changesets 3.0.3, Node 24.22.1, and npm 11.21.1 on 24 October 2026. Each case starts from a fresh npm workspace with both packages at 1.0.0. These checks exercised changeset creation and versioning locally; they did not publish to npm or run GitHub Actions.
package.json # private root, workspaces: ["packages/*"]
package-lock.json # generated by npm install
.changeset/config.json # configuration from Step 3
packages/core/package.json # @acme/core, version 1.0.0
packages/reports/package.json # @acme/reports, version 1.0.0
The reports package begins with this dependency:
{
"name": "@acme/reports",
"version": "1.0.0",
"dependencies": {
"@acme/core": "^1.0.0"
}
}
To reproduce the second row, configure the workspace above, run npm install to generate its lockfile, install Changesets as in Step 2, and commit the initial files on main. Then run:
npx @changesets/cli add --patch @acme/core --minor @acme/reports --message "Improve reports and fix core"
git add .changeset && git commit -m "Add release details"
npx @changesets/cli version
git diff -- packages/
Here are the results. Reset to the initial fixture for each case; these rows are independent, not sequential releases.
| Changeset input | updateInternalDependencies |
Core after | Reports after | Reports’ core range after |
|---|---|---|---|---|
| Core patch only | "patch" |
1.0.1 |
1.0.0 (unchanged) |
^1.0.0 (unchanged) |
| Core patch + reports minor | "patch" |
1.0.1 |
1.1.0 |
^1.0.1 |
| Core patch + reports minor | "minor" |
1.0.1 |
1.1.0 |
^1.0.0 (unchanged) |
| Core major only | "patch" |
2.0.0 |
1.0.1 |
^2.0.0 |
The first row leaves reports untouched because ^1.0.0 already accepts core 1.0.1. In the middle rows, reports has its own release; the policy determines whether its minimum core version moves. In the last row, core 2.0.0 no longer satisfies the old range, so Changesets updates that dependency and creates a patch release of reports. If reports itself has a breaking API change, declare its major bump explicitly too.
Changesets prereleases and snapshot releases
Prereleases are for a series of versions like 2.0.0-beta.0, 2.0.0-beta.1, usually on a branch:
npx @changesets/cli pre enter beta
npx @changesets/cli version
npx @changesets/cli publish # publishes to the "beta" dist-tag
# ...more changesets, more versions...
npx @changesets/cli pre exit # records the intent to return to stable
# Commit the pre-mode change; merge back to your release branch if applicable.
npx @changesets/cli version # produces stable versions and changelogs
npm run build --if-present
# Commit the version changes before publishing.
npx @changesets/cli publish # publishes the stable versions to latest
git push --follow-tags
Pre mode is recorded in .changeset/pre.json. In v3, changesets already consumed by a prerelease move to .changeset/pre/, and they supply the changelog entries for the eventual stable release, so you can edit or delete stale entries before the final version. Running changeset status once after upgrading migrates an old prerelease state.
Snapshot releases are throwaway versions for testing a PR, like 0.0.0-canary-20261020120000:
npx @changesets/cli version --snapshot canary
npx @changesets/cli publish --tag canary --no-git-tag
Always publish snapshots under a dist-tag other than latest, or users will install them by default. The snapshot.prereleaseTemplate option changes the suffix format, and snapshot.useCalculatedVersion bases snapshots on the real next version instead of 0.0.0.
What changed in v3, and how to migrate
Changesets v3 shipped on 11 August 2026, the first major release since v2 seven years earlier. Most tutorials you’ll find still describe v2, so these are the changes that break copied instructions:
| Area | v2 | v3 |
|---|---|---|
| Runtime | CommonJS, older Node | ESM-only, Node 22.11+ within 22.x, 24.x, or 26+, install size 16.1MB → 2.1MB |
Default baseBranch |
master |
main |
| Private packages | Versioned, not tagged | Neither, unless privatePackages is set |
| Formatting | "prettier" option |
"format" option, auto-detects Prettier, Oxfmt, Deno, or dprint |
access |
"private" alias accepted |
Use "restricted" |
| Tag command | changeset tag |
changeset git-tag |
| Status flag | --sinceMaster |
--since=main |
| Nothing to release | changeset version exits 0 |
Exits 1 |
| Peer dependency bumps | Dependents bumped major | Dependents bumped patch |
| Prerelease state | IDs in pre.json |
Consumed files move to .changeset/pre/ |
| GitHub Action | changesets/action@v1 |
changesets/action@v2 (required) |
To upgrade, update the CLI (npm install -D @changesets/cli@^3), work through the config changes above, and update the workflow. The action inputs were renamed:
changesets/action@v1 |
changesets/action@v2 |
|---|---|
version |
version-script |
publish |
publish-script |
commit |
commit-message |
title |
pr-title |
If you set a custom GITHUB_TOKEN environment variable for the v1 action, pass it through the github-token input now; v2 ignores the environment variable. And v2 pushes commits through the GitHub API by default, so commits are signed but your local checkout doesn’t move. Set push-with-git-cli: true if a later step depends on the local Git state.
Common problems
The first publish fails with a 402 or 403, or the package is private
access defaults to "restricted". Set it to "public" in config.json or in the package’s publishConfig.
“GitHub Actions is not permitted to create or approve pull requests.”
Enable the setting under Settings → Actions → General, and check the job has pull-requests: write.
CI doesn’t run on the Version Packages PR
PRs created with the built-in GITHUB_TOKEN don’t trigger workflows. Pass a GitHub App token to the version action’s github-token input, and check the action’s commit-message and the Changesets commit configuration. Setting commit: true defaults to skipping CI on version commits, even if you did not explicitly type [skip ci].
A script that calls changeset version now fails
v3 exits with code 1 when there’s nothing to release. Handle that case explicitly in snapshot and release scripts.
The changelog entry is wrong after merge
Edit the source changeset in its feature PR before merge. If it has already merged, submit a normal PR correcting the pending changeset on main, then let the Version Packages PR regenerate. That generated branch has already consumed the files, and later automation can overwrite manual changelog edits made directly on it.
changeset status reports changes you didn’t make
Your local baseBranch is out of date. Fetch it, or pass --since explicitly.
A deleted package still appears in a release
Remove its lines from any pending changeset files.
Changesets vs semantic-release vs Release Please
All three automate versions and changelogs. They differ on where the information comes from and whether a person signs off:
| Changesets | Release Please | semantic-release | |
|---|---|---|---|
| Version bump comes from | A changeset file written in the PR | Conventional commit messages | Conventional commit messages |
| Changelog text comes from | Human-written summaries | Commit messages | Commit messages |
| Human gate | Merge the Version Packages PR | Merge the release PR | None |
| Monorepos | Core design goal, dependency graph built in | Manifest config plus workspace plugins | Community plugins |
| Publishing | changeset publish to npm |
Not included | Core feature, via plugins |
| Ecosystem | JavaScript workspaces | Nineteen language strategies | Node-centric, plugins for others |
| Commit convention required | No | Yes | Yes |
Pick Changesets for a JavaScript monorepo with interdependent packages, or when you’d rather have people write the changelog than enforce a commit convention. Pick Release Please for a release PR driven by conventional commits, especially outside JavaScript. Pick semantic-release for push-to-publish with no gate. If your team already writes Conventional Commits and lints them with commitlint, the commit-based tools get that investment for free; Changesets asks for a different habit instead.
What the changelog looks like, and who it’s for
After a release, packages/reports/CHANGELOG.md gets an entry like this with the default generator:
# @acme/reports
## 1.3.0
### Minor Changes
- a1b2c3d: Add CSV export to the reports table. Exports respect the active filters and column order.
### Patch Changes
- e4f5a6b: Fix the export button staying disabled after clearing filters.
- Updated dependencies [e4f5a6b]
- @acme/core@2.1.1
Of the three tools, this is the one closest to a changelog a person would write, because a person did write it. That’s Changesets’ real advantage, and it’s why teams that care about their changelog often choose it over commit-based tools.
It’s still written for one audience: developers who install the package. The entries are per package, grouped by semver bump, and full of dependency updates, which is exactly right for someone deciding whether to upgrade @acme/reports. A customer of the product built from those packages doesn’t think in packages at all. Ten package changelogs with a “Minor Changes” heading each don’t tell them that exports got better this week. The changelog vs release notes post covers that gap, and the product changelog post covers what the customer-facing version should contain.
Where ReleasePad fits
ReleasePad is for the customer-facing side, and only that. It reads the merged pull requests and commits from GitHub and drafts one product-level entry in customer language for you to edit and publish to a hosted changelog page, an in-app widget, and Markdown, RSS, and JSON feeds. It doesn’t version packages, write CHANGELOG.md files, or publish to npm, so it doesn’t replace Changesets. The usual arrangement is Changesets owning package versions and package changelogs, and the product update drafted from the same merges. The GitHub integration page shows how the drafting works.
If your packages are the product, as with an open-source library, Changesets on its own is the whole answer.
Setup checklist
- Confirm a Node version matching
^22.11 || ^24 || >=26and a supported package manager, with workspaces configured for a monorepo. npm install -D @changesets/cliandnpx @changesets/cli init.- In
.changeset/config.json, setaccess(usually"public"), checkbaseBranch, and pick a changelog generator. - Add
fixed,linked, orprivatePackagesif your monorepo needs them. - Install the changeset bot, or add the
pr-statusworkflow, to remind contributors. - Enable “Allow GitHub Actions to create and approve pull requests”.
- Add the release workflow with
changesets/action@v2, and set up npm trusted publishing for it. - Merge a PR with a changeset, review the Version Packages PR it produces, and merge that to publish.
- Decide separately what your customers hear about each release.
Setup details are from the changesets.dev guides (getting started, versioning and publishing, automating, configuration, and the v2 to v3 migration guide), the Changesets v3 announcement, and the changesets/action README, read in October 2026. If a step here doesn’t match what you see, the official docs win.
Further reading
- semantic-release: How It Works and How to Set It Up with GitHub Actions: the push-to-publish, commit-driven alternative.
- Release Please: How Google’s Release Automation Works: the release-PR alternative driven by conventional commits.
- Semantic Versioning Explained: what the major, minor, and patch choices in each changeset promise.
- Keep a Changelog: The Format Explained: the conventions behind a readable
CHANGELOG.md. - How to Automate Release Notes from GitHub Commits: commit-based generators, AI scripts, and managed tools compared.
- Changelog Management Tool: CLI vs SaaS: when the generated file is the finished product, and when it isn’t.
Frequently Asked Questions
What is Changesets?
Changesets is an open-source tool for managing package versions and changelogs, built with a focus on monorepos. Contributors add a small Markdown file, a changeset, to their pull request saying which packages changed, whether each is a major, minor, or patch bump, and a summary for the changelog. When you release, Changesets combines all pending changesets, bumps the versions, updates each package's CHANGELOG.md and internal dependency ranges, and publishes to npm. It works in single-package repositories too.
How do I add a changeset?
Run npx @changesets/cli (or pnpm changeset) in your branch. The CLI asks which packages changed, the semver bump for each, and a summary, then writes a randomly named Markdown file to the .changeset folder. Commit that file with the rest of your pull request. For automation, pass package names to the bump flags, for example changeset add --minor @acme/reports --message 'Add CSV export', to create one without interactive prompts.
How do I set up Changesets with GitHub Actions?
Use changesets/action v2, which is required for Changesets v3. On every push to main it checks for pending changesets: if there are some, it runs changeset version and opens or updates a Version Packages pull request; if that pull request has been merged, it publishes the new versions and creates Git tags and GitHub Releases. You need contents and pull-requests write permissions, and the repository setting Allow GitHub Actions to create and approve pull requests. For npm, Changesets recommends trusted publishing with id-token: write only on the publish job.
Does every pull request need a changeset?
No. A changeset describes how a change should be released, so changes that don't need a release, such as tests, CI, or internal refactors, don't need one. The Changesets maintainers recommend against blocking pull requests that lack a changeset. Use the changeset bot or the pr-status action to remind authors instead. If you do want a strict rule, changeset status --since main fails CI when packages changed without a changeset, and changeset --empty records a deliberate no-release change.
What is the difference between Changesets, semantic-release, and Release Please?
All three automate versioning and changelogs, but they get their input differently. semantic-release and Release Please infer the version bump from conventional commit messages; semantic-release releases on every push, while Release Please opens a release pull request. Changesets ignores commit messages: contributors write an explicit changeset file with the bump type and a human-written summary, and releases go through a Version Packages pull request. Changesets is strongest in JavaScript monorepos where packages depend on each other.
What changed in Changesets v3?
Changesets v3, released in August 2026, is ESM-only and requires Node.js in the supported range ^22.11 || ^24 || >=26, with a much smaller install. The default baseBranch is now main, private packages are no longer versioned by default, the tag command is renamed git-tag, and changeset version exits with code 1 when there is nothing to release. Peer dependency updates now bump dependents by patch instead of major. GitHub workflows must move to changesets/action v2, which renamed its inputs and added sub-actions for a build, pack, and publish flow.
Ready to put this into practice?
Your changelog shouldn't be an afterthought.
ReleasePad makes it easy to publish great release notes — from a public changelog page to an in-app widget, GitHub integration, and analytics. Free to get started.
Get started — it's free