Skip to main content

semantic-release vs changesets vs release-it: Release Automation (2026)

·PkgPulse Team

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

Featuresemantic-releasechangesetsrelease-it
Automation level⚡ Fully automatedSemi-automatedManual
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.

Compare developer tooling and CI/CD packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.