Setup Changesets
Changesets automates versioning and changelog generation. Contributors add changeset files describing their changes; CI consumes them to bump versions, update changelogs, and publish packages.
Step 1 — Gather context
Before doing anything, detect or ask:
Detect automatically:
| Check | How |
|---|---|
| Package manager | Look for pnpm-lock.yaml, bun.lock/bun.lockb, yarn.lock, package-lock.yaml |
| Monorepo | pnpm-workspace.yaml, workspaces field in root package.json, or bun.workspace.ts |
| Already initialized | .changeset/ directory exists |
Hosting platform (informs CI workflow choice):
| Platform | How |
|---|---|
| GitHub | .github/ directory exists |
| GitLab | .gitlab-ci.yml or .gitlab/ directory exists |
| Bitbucket | bitbucket-pipelines.yml exists |
| Azure DevOps | azure-pipelines.yml exists |
| Gitea / Forgejo | Self-hosted; ask the user if no platform is detected |
Existing CI system (to know which workflow file to create or replace):
| CI system | Config file |
|---|---|
| GitHub Actions | .github/workflows/*.yml |
| GitLab CI | .gitlab-ci.yml |
| CircleCI | .circleci/config.yml |
| Azure Pipelines | azure-pipelines.yml |
| Bitbucket Pipelines | bitbucket-pipelines.yml |
| Jenkins | Jenkinsfile |
| Travis CI | .travis.yml |
| Drone CI | .drone.yml |
| AppVeyor | appveyor.yml |
Competing release tools (check package.json deps and config files):
| Tool | Detection |
|---|---|
| semantic-release | semantic-release in deps, OR .releaserc / .releaserc.{json,js,cjs,yaml,yml} / release.config.{js,cjs,ts} / "release" key in package.json |
| release-it | release-it in deps, OR .release-it.{json,js,ts,yaml,yml} / "release-it" key in package.json |
| standard-version | standard-version in deps, OR .versionrc / .versionrc.{json,js} / "standard-version" key in package.json |
| beachball | beachball in deps, OR beachball.config.json |
| release-please | release-please-config.json / .release-please-manifest.json |
| auto (Intuit) | auto in deps, OR .autorc / .autorc.{json,js} |
| Nx Release | nx in deps AND "release" key in nx.json |
| lerna | lerna.json exists |
| bumpp | bumpp in deps |
| changelogen | changelogen in deps |
If a competing tool is detected, ask before proceeding:
"I found
<tool(s)>in this repo. Changesets is a different approach to automated versioning. Do you want to migrate to changesets? I'll remove the old tool and its config, then set up changesets from scratch."
- Yes → follow the Migration section for the detected tool(s), then continue to Step 2
- No → stop here; do not set up changesets
Ask the user:
"Do you want to also create a reusable workflow in a
<org-or-user>/.githubrepo, so other repos can share the same release pipeline?"- Yes → follow Shared workflow setup after completing local setup
- No → inline the workflow in the appropriate CI config file
If monorepo: "Are any packages versioned together (always share the same version)?" →
fixedgroups"Are any packages private/internal and should be excluded from publishing?" →
ignorelist
Migration
Remove the old release tool before initializing changesets. Follow the subsection for each detected tool.
semantic-release
- Remove packages:
<pm> remove semantic-releaseand any@semantic-release/*plugins found inpackage.json - Delete config files (whichever exist):
.releaserc,.releaserc.json,.releaserc.js,.releaserc.cjs,.releaserc.yaml,.releaserc.yml,release.config.js,release.config.cjs,release.config.ts; also remove the"release"key if it appears insidepackage.json - Remove any
"semantic-release"call from the"release"script inpackage.json - In the detected CI config file(s), find the job/step that runs
semantic-release— delete the step or the entire job; note the filename so the new changeset workflow can reuse it - Secrets:
GH_TOKEN/GITHUB_TOKENandNPM_TOKENcan be reused as-is
release-it
- Remove packages:
<pm> remove release-itand any@release-it/*plugins - Delete config files (whichever exist):
.release-it.json,.release-it.js,.release-it.ts,.release-it.yaml,.release-it.yml,release-it.config.js,release-it.config.ts; also remove the"release-it"key frompackage.jsonif present - Remove any
"release-it"call from the"release"script inpackage.json - In the detected CI config file(s), find and remove the step running
release-it; note the filename for reuse - Secrets:
GITHUB_TOKENandNPM_TOKENcan be reused as-is
standard-version
- Remove packages:
<pm> remove standard-version - Delete config files (whichever exist):
.versionrc,.versionrc.json,.versionrc.js; also remove the"standard-version"key frompackage.jsonif present - Remove any
"standard-version"call from scripts inpackage.json - standard-version is often run locally rather than in CI; check for any CI step and remove if found
- Secrets: usually none;
NPM_TOKENreusable if present
beachball
- Remove packages:
<pm> remove beachball - Delete config files (whichever exist):
beachball.config.json; remove any"beachball"key from root or package-levelpackage.jsonfiles - Remove
change,checkchange, andreleasescripts inpackage.jsonthat callbeachball - In CI config, find and remove the step running
beachball release - Secrets:
NPM_TOKENreusable
release-please
- Remove packages:
<pm> remove release-please(if installed as a CLI dep); the GitHub Action needs no package removal - Delete config files:
release-please-config.json,.release-please-manifest.json - No
package.jsonscripts to remove (release-please is entirely CI-driven) - In
.github/workflows/, find and delete the workflow usinggoogleapis/release-please-action - Secrets:
GITHUB_TOKENandNPM_TOKENreusable
auto (Intuit)
- Remove packages:
<pm> remove autoand any@auto-it/*plugins - Delete config files (whichever exist):
.autorc,.autorc.json,.autorc.js - Remove any
"auto release"or"auto shipit"calls from scripts inpackage.json - In CI config, find and remove the step running
auto release; note the filename for reuse - Secrets:
GH_TOKENcan be reused asGITHUB_TOKEN;NPM_TOKENreusable
Nx Release
- No separate package to remove —
nxitself provides the release functionality and stays installed - Remove the
"release"key fromnx.json; remove anynx releasetargets fromproject.jsonfiles - Remove any
"nx release"calls from scripts inpackage.json - In CI config, find and remove the
nx releasestep - Secrets:
GITHUB_TOKENandNPM_TOKENreusable
lerna
- Remove packages:
<pm> remove lerna - Delete config files:
lerna.json - Remove any
"lerna publish"or"lerna version"calls from scripts inpackage.json - In CI config, find and remove the step running
lerna publish - Secrets:
GH_TOKEN/GITHUB_TOKENandNPM_TOKENreusable
bumpp
- Remove packages:
<pm> remove bumpp - Delete config: no dedicated config file; remove any
"bumpp"key frompackage.jsonif present - Remove any
"bumpp"calls from scripts inpackage.json - In CI config, find and remove any
bumppstep - Secrets:
NPM_TOKENreusable if publishing was wired up
changelogen
- Remove packages:
<pm> remove changelogen - Delete config: no dedicated config file; remove any
"changelogen"key frompackage.jsonif present - Remove any
"changelogen"calls from scripts inpackage.json - In CI config, find and remove any
changelogenstep - Secrets:
GITHUB_TOKENreusable if GitHub releases were used
Step 2 — Initialize
# pnpm
pnpm dlx @changesets/cli init
# bun
bunx @changesets/cli init
# npm / yarn
npx @changesets/cli init
This creates .changeset/config.json and .changeset/README.md.
Step 3 — Configure .changeset/config.json
Replace the generated config with appropriate settings:
Single package:
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "main"
}
Monorepo with grouped packages:
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"access": "public",
"baseBranch": "main",
"fixed": [["package-a", "package-b"]],
"linked": [],
"ignore": ["internal-tools"],
"updateInternalDependencies": "patch"
}
Key decisions:
"access": "public"— required for publishing scoped packages (@scope/name) publicly; private packages ignore this"fixed"— packages that must always share the exact same version number"linked"— packages that share the highest bump type but keep independent version numbers"ignore"— packages excluded from changeset versioning entirely (e.g.examples, internal CLIs)"commit": false— recommended;changeset versionwon't auto-commit, giving you control
Step 4 — Add scripts to package.json
{
"scripts": {
"version": "changeset version",
"release": "changeset publish",
"cs": "changeset"
}
}
cs is a shorthand for adding changesets during development. version and release are called by CI.
If the project has a build step that must run before publishing, update release:
"release": "pnpm build && changeset publish"
Step 5 — Set up the CI release workflow
Use the CI system detected in Step 1. If the project is on GitHub, prefer Option A or B below. For all other CI systems, use the platform-specific template.
GitHub Actions — Option A (inline workflow)
Create .github/workflows/release.yml:
name: release
on:
push:
branches: [main]
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Insert package manager setup here (see below)
- run: <install-command>
- run: <build-command> # remove if no build step
- name: Create Release PR or Publish
uses: changesets/action@v1
with:
version: <pm> run version
publish: <pm> run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Replace <pm> with pnpm, bun, npm, or yarn. Replace <install-command> with pnpm install --frozen-lockfile, bun install, etc.
Package manager setup snippets:
pnpm:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
bun:
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
node-version: 22
npm/yarn:
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm # or: yarn
Token note: GITHUB_TOKEN is sufficient for most setups. If your repo has branch protection rules that require CI status checks on the "Version Packages" PR, you'll need a PAT with repo scope stored as a secret (e.g. RELEASE_TOKEN) and passed as GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} — this lets the action's commits trigger CI.
GitHub Actions — Option B (shared workflow)
Use this when you want all your repos to share one release pipeline definition. Changes to the shared workflow apply to all repos at once.
In the <user-or-org>/.github repo, create .github/workflows/release-changeset.yml:
name: release-changeset
on:
workflow_call:
inputs:
node-version:
type: string
default: '22'
outputs:
published:
description: 'Whether packages were published'
value: ${{ jobs.release.outputs.published }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
outputs:
published: ${{ steps.changesets.outputs.published }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Add your standard setup steps here (package manager, node, install, build)
- name: Create Release PR or Publish
id: changesets
uses: changesets/action@v1
with:
version: <pm> run version
publish: <pm> run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
In each consuming repo, create .github/workflows/release.yml:
name: release
on:
push:
branches: [main]
jobs:
release:
uses: <user-or-org>/.github/.github/workflows/release-changeset.yml@main
secrets: inherit
GitLab CI (.gitlab-ci.yml)
release:
stage: release
image: node:22
only:
- main
script:
- <install-command>
- <build-command> # remove if no build
- <pm> run version
- git config user.email "ci@bot" && git config user.name "CI Bot"
- git add . && git commit -m "chore: version packages" || true
- git push "https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:main || true
- <pm> run release
variables:
NPM_TOKEN: $NPM_TOKEN
Secrets: add NPM_TOKEN and GITLAB_TOKEN (a project access token with write_repository scope) in Settings → CI/CD → Variables.
Note: changesets/action is GitHub-only. On GitLab you call changeset version + changeset publish directly and handle the git commit/push yourself. The "Version Packages" PR pattern is not available — version bump and publish happen in one CI run.
CircleCI (.circleci/config.yml)
version: 2.1
jobs:
release:
docker:
- image: cimg/node:22.0
steps:
- checkout
- run: <install-command>
- run: <build-command> # remove if no build
- run: <pm> run version
- run: git config user.email "ci@bot" && git config user.name "CI Bot"
- run: git add . && git commit -m "chore: version packages" || true
- run: git push || true
- run: <pm> run release
workflows:
release:
jobs:
- release:
filters:
branches:
only: main
Secrets: add NPM_TOKEN in Project Settings → Environment Variables in the CircleCI UI.
Bitbucket Pipelines (bitbucket-pipelines.yml)
pipelines:
branches:
main:
- step:
name: Release
image: node:22
script:
- <install-command>
- <build-command> # remove if no build
- <pm> run version
- git config user.email "ci@bot" && git config user.name "CI Bot"
- git add . && git commit -m "chore: version packages" || true
- git push || true
- <pm> run release
deployment: production
Secrets: add NPM_TOKEN in Repository settings → Repository variables in the Bitbucket UI.
Azure Pipelines (azure-pipelines.yml)
trigger:
branches:
include: [main]
pool:
vmImage: ubuntu-latest
steps:
- task: NodeTool@0
inputs:
versionSpec: '22'
- script: <install-command>
displayName: Install dependencies
- script: <build-command>
displayName: Build # remove if no build
- script: |
<pm> run version
git config user.email "ci@bot"
git config user.name "CI Bot"
git add . && git commit -m "chore: version packages" || true
git push || true
<pm> run release
displayName: Version and publish
env:
NPM_TOKEN: $(NPM_TOKEN)
Secrets: add NPM_TOKEN as a pipeline variable (secret) in Azure DevOps → Pipelines → Edit → Variables.
Jenkins (Jenkinsfile)
pipeline {
agent any
triggers { pollSCM('H/5 * * * *') }
stages {
stage('Release') {
when { branch 'main' }
steps {
sh '<install-command>'
sh '<build-command>' // remove if no build
sh '<pm> run version'
sh 'git config user.email "ci@bot" && git config user.name "CI Bot"'
sh 'git add . && git commit -m "chore: version packages" || true'
sh 'git push || true'
withCredentials([string(credentialsId: 'NPM_TOKEN', variable: 'NPM_TOKEN')]) {
sh '<pm> run release'
}
}
}
}
}
Secrets: add NPM_TOKEN via Manage Jenkins → Credentials as a secret text credential.
Travis CI (.travis.yml)
language: node_js
node_js: '22'
branches:
only: [main]
install:
- <install-command>
script:
- <build-command> # remove if no build
deploy:
provider: script
script: >-
<pm> run version &&
git config user.email "ci@bot" && git config user.name "CI Bot" &&
(git add . && git commit -m "chore: version packages" || true) &&
(git push || true) &&
<pm> run release
on:
branch: main
Secrets: add NPM_TOKEN via Travis CI → Repository settings → Environment Variables.
Drone CI (.drone.yml)
kind: pipeline
type: docker
name: release
trigger:
branch: [main]
event: [push]
steps:
- name: release
image: node:22
environment:
NPM_TOKEN:
from_secret: npm_token
commands:
- <install-command>
- <build-command> # remove if no build
- <pm> run version
- git config user.email "ci@bot" && git config user.name "CI Bot"
- git add . && git commit -m "chore: version packages" || true
- git push || true
- <pm> run release
Secrets: add npm_token via Drone → Repository settings → Secrets.
Step 6 — Add secrets to the repository
Add the following secrets in your platform's secret/variable settings:
NPM_TOKEN— an npm automation token (create at npmjs.com → Access Tokens → Generate New Token → Automation)RELEASE_TOKEN— optional PAT (GitHub only), needed if branch protection requires CI on the "Version Packages" PR
Step 7 — Verify
# Add a test changeset
npx changeset add --empty
# Check it was created
ls .changeset/
# Check the release workflow is valid (GitHub only)
gh workflow list
What the automated flow looks like
Once set up, the full cycle is:
- Developer adds a changeset file to their PR (see
add-changesetskill) - PR merges to main
- CI runs
changesets/action— detects new changesets, opens/updates a "Version Packages" PR (GitHub only; on other platforms, version bump and publish happen in one CI run) - When ready to release, merge the "Version Packages" PR
- CI runs again — no pending changesets, so it runs
releaseand publishes to npm
The "Version Packages" PR is fully managed by the action. Do not manually edit CHANGELOG.md or the version numbers it touches.