Hand-written version bumps and changelogs drift out of sync with reality the moment more than one contributor is shipping changes — a minor release gets tagged as patch, an entry is forgotten, or two people bump the same version number in parallel pull requests. This section covers automating both concerns with Changesets, the de facto standard for versioning npm packages from monorepos and single-package repositories alike, and shows how it interacts with the exports field your dual-format build already relies on.

Prerequisites

Before adopting automated versioning, confirm:


Canonical Configuration Block

Initialize Changesets once per repository; it creates a .changeset/config.json file that governs how versions are bumped and changelogs are written.

npx changeset init
{
  "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

access: "public" is required for scoped packages (@acme/sdk) to publish publicly rather than defaulting to npm’s private-by-default behavior for scopes. updateInternalDependencies: "patch" controls how a bump in one workspace package ripples to internal dependents — set it to "minor" if you want internal consumers to receive a larger bump whenever a dependency changes.


Release Flow

Changesets release pipeline A four-stage pipeline showing a contributor adding a changeset, CI running the version command to bump versions and write changelogs, then publishing to npm, and finally updating the dist-tag. Versioning & Release Flow Add changeset per pull request Version bump + changelog Publish npm publish dist-tag latest / next

Step-by-Step Implementation

Step 1 — Record intent-to-release with a changeset

Whenever a pull request changes published behavior, run changeset to describe it. This is an interactive prompt that writes a markdown file, not a version bump itself:

npx changeset
🦋  Which packages would you like to include? · @acme/sdk
🦋  Which packages should have a major bump? · (none selected)
🦋  Which packages should have a minor bump? · (none selected)
🦋  Which packages should have a patch bump? · @acme/sdk
🦋  Please enter a summary for this change
🦋  Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition

This produces a file like .changeset/tall-cats-jam.md:

---
"@acme/sdk": patch
---

Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition

Commit this file alongside the code change in the same pull request. Multiple changesets can accumulate on main between releases — nothing is bumped or published until Step 2 runs.

HAZARD PREVENTION

Symptom: A release ships with no changelog entry for a change that clearly altered behavior.

Root cause: The pull request that introduced the change never ran npx changeset, so there was nothing for the version command to consume.

Fix: Add a CI check that fails a pull request touching src/ or package.json if no .changeset/*.md file is present in the diff (Changesets ships a changeset status --since=main command for exactly this check).

Step 2 — Bump versions and generate the changelog

Run the version command to consume every pending changeset, bump each affected package’s version field according to semver, delete the consumed changeset files, and prepend entries to CHANGELOG.md:

npx changeset version
--- a/package.json
+++ b/package.json
@@
-  "version": "2.0.0",
+  "version": "2.0.1",
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@
+## 2.0.1
+
+### Patch Changes
+
+- Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
+
 ## 2.0.0

This step never touches the npm registry — it only edits files in your working tree. Review and commit the result like any other change.

Step 3 — Publish with the correct dist-tag

npx changeset publish

changeset publish runs npm publish for every package whose version no longer matches what’s on the registry, tagging each with latest by default. For prerelease builds, see Managing Prerelease and dist-tags on npm, which covers npx changeset pre enter beta and publishing without disturbing the default tag.

HAZARD PREVENTION

Symptom: A patch release accidentally becomes the default install for users, even though it was meant as an internal preview.

Root cause: npm publish (and changeset publish) applies the latest dist-tag unless told otherwise, regardless of the version’s semver prerelease suffix.

Fix: Enter Changesets’ pre-release mode (npx changeset pre enter <tag>) before publishing previews, which automatically applies the matching dist-tag instead of latest.

Step 4 — Automate the release PR in CI

The changesets/action GitHub Action watches main for pending changesets. When it finds them, it opens (or updates) a “Version Packages” pull request running Step 2 automatically; when that PR is merged, the same workflow run publishes to npm:

name: Release
on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write
  id-token: write

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - name: Create release PR or publish
        uses: changesets/action@v1
        with:
          publish: npx changeset publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

For the full picture of the surrounding publish pipeline — including provenance attestation and OIDC — see Automating npm Releases with GitHub Actions.


Tooling Validation

Check for pending changesets before merging, so a release branch never silently loses a changelog entry:

npx changeset status --since=main

Sample output when a changeset is missing:

🦋  warn It seems this repo does not have any changesets.
🦋  warn Warning: this workflow will not create a release.

Sample output when everything is wired correctly:

🦋  info @acme/sdk: patch
🦋  This Changeset release will release 1 package

Compatibility Matrix

Feature Changesets Conventional commits + semantic-release Manual npm version
Monorepo/workspace aware Yes (native) Partial (plugins required) No
Changelog generation Yes, per-package Yes No
Requires strict commit message format No Yes No
Prerelease / dist-tag support Yes (changeset pre) Yes (branch-based) Manual
Human-editable release notes Yes (edit the changeset file) No (derived from commits) Yes

Guides in This Section



Back to CI/CD, Publishing & npm Provenance