Versioning a monorepo with changesets Jump to heading
Versioning one package is a decision made at release time: look at what changed, pick major, minor or patch. In a monorepo with twenty packages, nobody can reconstruct that at release time β which packages changed, how significantly, and which dependents need bumping too. The changesets approach moves the decision to the pull request, where the author knows. Each pull request adds a small file naming the packages it affects and the bump each needs, with a sentence for the changelog. A release step later reads all pending changeset files, computes versions, updates manifests and changelogs, and the tags follow. This page sets up the workflow with the changesets tool for JavaScript workspaces and shows how the same pattern works elsewhere, within monorepo branch topology.
When to use this approach Jump to heading
- A monorepo publishes several packages with independent versions.
- Version bumps and changelogs are decided at release time and often wrong or missing.
- Packages depend on each other, so bumping one should bump its dependentsβ ranges.
- Per-package tags are in use, as in tagging packages independently in a monorepo.
Step 1 β Initialise changesets in the repository Jump to heading
The tool adds a .changeset/ directory with configuration. Pending changes will live there as small Markdown files until a release consumes them.
npm install --save-dev @changesets/cli
npx changeset init
cat .changeset/config.json {
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
} updateInternalDependencies: patch means that when a package is released, packages depending on it get their dependency range updated and a patch release of their own, so the published graph stays consistent.
Step 2 β Add a changeset in each pull request Jump to heading
Authors run one command, choose the packages and bump types, and write a summary. The result is a file with a random name β which, as with changelog fragments instead of one changelog, means pull requests never conflict over it.
npx changeset
# ? Which packages would you like to include? @acme/money, billing-api
# ? Which packages should have a major bump? (none)
# ? Which packages should have a minor bump? billing-api
# ? Summary: Round credit note totals half-even.
cat .changeset/brave-otters-sing.md ---
"@acme/money": patch
"billing-api": minor
---
Round credit note totals half-even. Step 3 β Require a changeset in CI Jump to heading
A pull request that changes a published package without a changeset will release nothing. Check for it, with an escape for changes that need no release.
jobs:
changeset:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: npm ci
- run: npx changeset status --since=origin/${{ github.base_ref }} changeset status --since fails if packages changed since the base branch without a changeset covering them. For changes that genuinely need no release β tests, internal tooling β an empty changeset (npx changeset --empty) records the decision explicitly.
Step 4 β Let a release pull request apply the versions Jump to heading
Rather than versioning on every merge, automation keeps a single βVersion packagesβ pull request open, updated whenever new changesets land on main. It shows exactly what the next release will contain. Merging it is the release decision.
# .github/workflows/release.yml
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write, id-token: write }
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: npm ci
- uses: changesets/action@v1
with:
version: npx changeset version # updates versions, dependents, CHANGELOG.md files
publish: npx changeset publish # publishes and creates pkg@version tags
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} Step 5 β Handle pre-releases and stable lines Jump to heading
For release candidates, changesets has a pre-release mode that produces versions like 2.5.0-rc.0 from the same files. Enter it on a branch, release candidates as needed, and exit to produce the final versions.
npx changeset pre enter rc
npx changeset version # 2.5.0-rc.0 for packages with pending changes
# β¦ test, more changesets, version again β rc.1 β¦
npx changeset pre exit
npx changeset version # 2.5.0 Pre-release tagging in general is covered in tagging pre-releases and release candidates.
Step 6 β Apply the pattern outside JavaScript Jump to heading
The tool is JavaScript-specific, but the pattern is not. Any monorepo can use change files naming packages and bump types, a CI check that requires them, and a release script that reads them, computes versions and tags name@version. Equivalents exist for several ecosystems; a small script is enough for many repositories.
# Minimal pattern: changes/<pr>.yml with { package: bump } and a summary
for f in changes/*.yml; do yq -r 'to_entries[] | select(.key != "summary") | "\(.key) \(.value)"' "$f"; done |
sort | awk '{rank["patch"]=1; rank["minor"]=2; rank["major"]=3; if (rank[$2] > best[$1]) {best[$1]=rank[$2]; b[$1]=$2}} END {for (p in b) print p, b[p]}' Step 7 β Review the release pull request properly Jump to heading
The release pull request is the one place where every pending version bump and changelog entry is visible together. Read it as a release review: are the bump levels right, are the changelog lines written for users, did any package get a major bump by mistake? Fix problems by editing the pending changeset files on main, and the release pull request updates itself.
gh pr view "$(gh pr list --head changeset-release/main --json number --jq '.[0].number')" --json files \
--jq '.files[] | select(.path | endswith("package.json")) | .path' Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Do changeset files cause merge conflicts? Jump to heading
No. Each has a randomly generated name, so concurrent pull requests add different files. They are deleted together by the release step.
Can a changeset be edited after merging? Jump to heading
Yes, until it is consumed by a release. Edit the file on main through a normal pull request to change the bump or wording.
What happens to packages with no changesets? Jump to heading
Nothing: they keep their version and get no tag. Only internal dependency updates can bump a package without its own changeset, and only as configured.
Related Jump to heading
- Monorepo Branch Topology β the parent topic.
- Automating Changelog Generation with semantic-release β the commit-message-based alternative.
- Release Branches in a Monorepo β when packages also need maintenance lines.
- Generating Release Notes from Commit Trailers β another way to capture user-facing notes.