semantic-release vs changesets vs release-it: Release Automation (2026)
TL;DR
semantic-release automates versioning and publishing based on commit message conventions (Conventional Commits) — no manual version bumps, CHANGELOG generated automatically, fully hands-off. changesets uses a pull-request-based workflow — developers add changeset files describing what changed, then a CI bot opens PRs to bump versions and publish. release-it is the most flexible manual release tool — interactive CLI, supports any versioning strategy, hooks for custom scripts. In 2026: semantic-release for fully automated CI/CD, changesets for team-based open source projects, release-it when you want control.
Key Takeaways
- semantic-release: ~2M weekly downloads — fully automated, commit-message-driven, zero human interaction
- changesets: ~3M weekly downloads — PR-based workflow, great for open source and monorepos
- release-it: ~2M weekly downloads — interactive CLI, flexible, not opinionated about commit style
- semantic-release requires Conventional Commits (
feat:,fix:,chore:) for version detection - changesets uses
.changeset/markdown files in the repo — devs describe changes in prose - All three support monorepos — changesets has the best monorepo support
The Release Problem
Without automation:
1. Manually decide: is this a major, minor, or patch?
2. Update version in package.json
3. Write CHANGELOG entry
4. git commit && git tag v1.2.3
5. git push && git push --tags
6. npm publish
→ Error-prone, inconsistent, easy to forget a step
With automation:
semantic-release: git commit → CI detects → publishes automatically
changesets: PR merged → CI bot bumps version → publish
release-it: `release-it` → interactive prompts → done
semantic-release
semantic-release — fully automated releases:
How it works
1. Developer commits with Conventional Commits:
"feat: add new package comparison endpoint"
"fix: correct health score calculation"
"BREAKING CHANGE: remove deprecated /v1 API"
2. CI runs semantic-release on main branch
3. semantic-release analyzes commits since last release:
- feat → minor version bump (1.2.0 → 1.3.0)
- fix → patch version bump (1.2.0 → 1.2.1)
- BREAKING CHANGE → major version bump (1.2.0 → 2.0.0)
4. semantic-release:
- Creates GitHub release with auto-generated changelog
- Publishes to npm
- Updates package.json version (in release artifacts)
- Commits release notes back to repo
.releaserc.json configuration
{
"branches": ["main", "next", {"name": "beta", "prerelease": true}],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}]
]
}
GitHub Actions workflow
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
permissions:
contents: write # Create tags and releases
issues: write # Comment on released issues
pull-requests: write # Comment on merged PRs
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # semantic-release needs full git history
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
Conventional Commits cheat sheet
# patch: 1.0.0 → 1.0.1
git commit -m "fix: handle null healthScore in comparison"
git commit -m "perf: optimize package search query"
git commit -m "docs: update API documentation"
# minor: 1.0.0 → 1.1.0
git commit -m "feat: add weekly download trend chart"
git commit -m "feat(api): add /packages/compare endpoint"
# major: 1.0.0 → 2.0.0
git commit -m "feat!: remove deprecated v1 API endpoints"
git commit -m "feat: new auth system
BREAKING CHANGE: JWT tokens now required for all endpoints"
# No release:
git commit -m "chore: update dependencies"
git commit -m "ci: add coverage reporting"
git commit -m "test: add unit tests for calculator"
changesets
changesets — PR-based release workflow:
How it works
1. Developer opens a PR
2. Developer adds a changeset file:
npx changeset
→ Interactive prompt: what packages changed? major/minor/patch? What changed?
→ Creates .changeset/a-random-slug.md
3. Changesets bot comments on the PR:
"This PR will release @pkgpulse/api@1.2.0 when merged"
4. PR is merged to main
5. Changesets GitHub Action opens a "Version PR" automatically:
- Bumps version numbers
- Updates CHANGELOG.md
- Consumes all pending changeset files
6. Team merges the Version PR → GitHub Action publishes to npm
Setup
npm install -D @changesets/cli
npx changeset init
# Creates .changeset/config.json
.changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
Adding a changeset
# Run this when you have changes to release:
npx changeset
# Interactive prompts:
# ? Which packages should be included?
# ● @pkgpulse/api
# ○ @pkgpulse/ui
# ? Which type of change is this?
# ○ patch (0.0.1)
# ● minor (0.1.0) ← selected
# ○ major (1.0.0)
# ? Describe the changes — will appear in CHANGELOG:
# Added support for comparing multiple packages at once
# Creates: .changeset/lemon-boots-help.md
<!-- .changeset/lemon-boots-help.md (committed to the PR): -->
---
"@pkgpulse/api": minor
---
Added support for comparing multiple packages at once via the new `/compare` endpoint.
Users can now pass `?names=react,vue,angular` to get a comparison in a single request.
GitHub Actions workflow
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: npm run release # Script that calls: changeset publish
version: npm run version # Script that calls: changeset version
commit: "chore: release"
title: "chore: release packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Monorepo support (killer feature)
// packages/api/package.json
{
"name": "@pkgpulse/api",
"version": "1.2.0"
}
// packages/ui/package.json
{
"name": "@pkgpulse/ui",
"version": "0.5.0"
}
# Changeset for specific packages:
npx changeset
# Select only @pkgpulse/api (not @pkgpulse/ui)
# → Only api gets a version bump
# Multiple packages in one changeset:
# → Each can have different bump types (api: minor, ui: patch)
release-it
release-it — interactive release CLI:
Basic usage
# Interactive release (prompts for bump type):
npx release-it
# Non-interactive (pass bump type):
npx release-it patch # 1.2.0 → 1.2.1
npx release-it minor # 1.2.0 → 1.3.0
npx release-it major # 1.2.0 → 2.0.0
# Dry run (see what would happen):
npx release-it --dry-run
.release-it.json configuration
{
"git": {
"commitMessage": "chore: release v${version}",
"tagName": "v${version}",
"tagAnnotation": "Release v${version}",
"requireCleanWorkingDir": true,
"requireUpstream": true
},
"npm": {
"publish": true,
"publishPath": ".",
"tag": "latest"
},
"github": {
"release": true,
"releaseName": "v${version}",
"draft": false,
"autoGenerate": true
},
"hooks": {
"before:init": ["npm run lint", "npm run test"],
"after:bump": "npm run build",
"after:release": "echo Successfully released ${name} v${version} to ${repo.repository}"
}
}
With conventional-changelog
// .release-it.json
{
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular",
"infile": "CHANGELOG.md"
}
},
"git": {
"requireCommits": true,
"commitMessage": "chore(release): ${version}"
},
"github": {
"release": true
},
"npm": {
"publish": true
}
}
Feature Comparison
| Feature | semantic-release | changesets | release-it |
|---|---|---|---|
| Automation level | ⚡ Fully automated | Semi-automated | Manual |
| Requires Conventional Commits | ✅ | ❌ | ❌ (optional) |
| PR-based workflow | ❌ | ✅ | ❌ |
| Monorepo support | ✅ (plugin) | ✅ First-class | ✅ (partial) |
| Interactive CLI | ❌ | ❌ | ✅ |
| GitHub releases | ✅ | ✅ | ✅ |
| npm publish | ✅ | ✅ | ✅ |
| Custom changelog | ✅ | ✅ | ✅ |
| Pre-release channels | ✅ | ✅ | ✅ |
| Weekly downloads | ~2M | ~3M | ~2M |
When to Use Each
Choose semantic-release if:
- Fully automated releases with no human decision-making
- Team already uses Conventional Commits
- You want CI/CD to handle everything automatically
- Single-package library with continuous deployment
Choose changesets if:
- Open source project with multiple contributors
- Monorepo with multiple packages that need independent versioning
- Want PR authors to describe their changes in human-readable prose
- Need the Changesets GitHub bot to automate the Version PR
Choose release-it if:
- Want control over when and how you release
- Don't use Conventional Commits and don't want to change
- Simple interactive release flow
- Small team or solo developer
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on semantic-release v24.x, @changesets/cli v2.x, and release-it v17.x.