Back to publishing & release Automate semantic versioning Manage release channels Harden the supply chain

Publishing Prereleases with Changesets Pre Mode

Changesets normally turns accumulated changeset files into stable version bumps. For a major release that needs weeks of testing, you want something different: a series of prereleases (5.0.0-next.0, 5.0.0-next.1, ...) published under a separate dist-tag, while latest stays on the current stable version. Changesets' pre mode does exactly that. This guide enters and exits pre mode, explains how versions are calculated while in it, sets up the branch and CI workflow, and covers the mistakes that most often leave a repository stuck in pre mode or publish a prerelease as latest. The same mechanism works for any tag name, so the steps apply equally to alpha, beta or rc cycles.

How pre mode works

Entering pre mode writes a .changeset/pre.json file that records the prerelease tag and the version each package had when pre mode started. While it exists:

  • changeset version produces prerelease versions (5.0.0-next.0, then -next.1, ...) instead of stable ones, based on the highest bump requested by changesets since entering pre mode.
  • changeset publish publishes with the pre tag (next) as the dist-tag.
  • Changeset files are not deleted after versioning; pre.json lists which ones have been consumed, so the final stable release can still generate a complete changelog.

Release channels in general are covered in Release Channels and Dist-Tags.

The Changesets pre mode cycle Enter pre mode with a tag, add changesets, version and publish prereleases repeatedly to the next tag, then exit pre mode and release the stable version to latest. changeset pre enter next writes .changeset/pre.json version + publish 5.0.0-next.0 to @next repeat per iteration -next.1, -next.2 ... changeset pre exit next version is stable version + publish 5.0.0 to @latest
While pre.json exists, every version is a prerelease published under the pre tag.

Running a prerelease cycle

Use a dedicated branch so stable patch releases can continue from main:

git switch -c next main
pnpm changeset pre enter next
git add .changeset/pre.json && git commit -m "Enter prerelease mode (next)"
git push -u origin next

Work lands on next with changeset files as usual — including major changesets for breaking changes. When you want a prerelease:

pnpm changeset version     # bumps to 5.0.0-next.0 (for a major) and updates changelogs
git commit -am "Version packages (next)"
pnpm changeset publish     # publishes with dist-tag "next", pushes git tags
git push --follow-tags

Consumers opt in with npm install @acme/core@next. Each further version and publish produces 5.0.0-next.1, -next.2, and so on.

To finish:

pnpm changeset pre exit
pnpm changeset version     # now produces 5.0.0 from all consumed changesets
git commit -am "Release 5.0.0"
pnpm changeset publish     # publishes with "latest"

Then merge next back into main so main carries the new major. Delete or reset the next branch afterwards, so the next prerelease cycle starts from a clean branch cut from the new main rather than from the finished cycle's history.

Repository state during pre mode The changeset directory contains pre.json recording mode, tag and initial versions, plus changeset files that remain after versioning and are listed as consumed. .changeset/ config.json pre.json mode: pre, tag: next, initialVersions brave-lions-sing.md major: new API (consumed) quiet-owls-run.md patch: fix (consumed) fuzzy-cats-jump.md minor: not yet versioned
Changeset files stay in place during pre mode so the final stable release has a complete changelog.

CI workflow

With the Changesets GitHub Action, the same workflow handles both branches; pre mode is driven by the presence of pre.json:

name: release
on:
  push:
    branches: [main, next]

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

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with: { node-version: 24, cache: pnpm, registry-url: https://registry.npmjs.org }
      - run: pnpm install --frozen-lockfile
      - run: pnpm -r build
      - name: Guard - pre mode only on next
        run: |
          if [ "$GITHUB_REF_NAME" = "main" ] && [ -f .changeset/pre.json ]; then
            echo "pre.json must not exist on main"; exit 1
          fi
      - uses: changesets/action@v1
        with:
          version: pnpm changeset version
          publish: pnpm changeset publish

The guard prevents the most damaging mistake — merging next into main while still in pre mode, which would turn every stable release from main into a prerelease.

Common problems

Versions look wrong. In pre mode, the bump type is computed from all changesets since entering pre mode. If the first changeset was minor, you get 4.9.0-next.0; a later major changeset moves the next prerelease to 5.0.0-next.1. That is expected — pre mode never goes backwards.

A prerelease went to latest. Usually pre.json was deleted by hand instead of with pre exit, or a custom publish script ignored the tag. Check npm dist-tag ls and fix latest as described in Fixing a Wrong latest Dist-Tag.

Packages without changes in the cycle. In independent mode, only packages with changesets get prereleases. Dependents that are bumped because a dependency changed also enter the prerelease. Fixed groups move together.

Merging main into next. Stable patches from main can be merged into next safely; their changeset files join the cycle and appear in the next prerelease.

Pre mode versus snapshot releases Compares Changesets pre mode with snapshot releases on version format, changelogs, intended use and cleanup. pre mode snapshot Version format 5.0.0-next.3 0.0.0-pr123-20260114 Changelogs accumulated, kept none Consumes changesets recorded in pre.json no Typical use release candidates testing one PR Exit step changeset pre exit none
Pre mode is for planned release cycles; snapshots are for throwaway builds of one change.

Snapshot releases are covered in Publishing Canary Releases from Pull Requests.

Choosing the pre tag and version identifiers

The tag you pass to changeset pre enter becomes both the dist-tag and the prerelease identifier in version numbers: pre enter next yields 5.0.0-next.0, while pre enter rc yields 5.0.0-rc.0. Pick names that tell consumers how stable a build is. A common progression is alpha for incomplete features and unstable APIs, beta for feature-complete builds with known bugs, and rc for builds you intend to ship unchanged unless a blocker appears. Many projects simplify to a single next tag for the whole cycle. Whatever you choose, semantic versioning orders prerelease identifiers alphabetically — alpha < beta < next < rc — so moving from beta to rc keeps versions increasing, while moving from rc back to beta would not.

Communicating prereleases

Prereleases only produce feedback if people install them. Changesets generates changelog entries for each prerelease, and the Changesets GitHub Action can create GitHub releases for them; mark those as pre-releases so they do not appear as the latest release on the repository page. In release notes, lead with what is new and what is expected to change before the stable release, and give the exact install command. For major versions, publish a migration guide early — during the first prereleases — so early adopters can report where it is unclear, and keep it updated as breaking changes land.

Monorepos with many packages

In a monorepo, pre mode applies to the whole repository: every package that is versioned while pre.json exists gets a prerelease version. Packages unrelated to the major release but changed during the cycle (a documentation tool, an unrelated utility) will also be published as prereleases, which is often not what you want. Two options help. Keep unrelated fixes on main and release them from there during the cycle, merging main into next afterwards. Or use the ignore option in .changeset/config.json for packages that should never be versioned from the next branch. Decide before entering pre mode, and list the packages that are part of the prerelease in the cycle's tracking issue.

Worked example: a six-week major release

A UI library enters pre mode on a next branch with tag next. Over six weeks it publishes seven prereleases; three internal applications install @next and report two regressions, fixed in -next.4 and -next.6. Meanwhile, two security patches ship from main as 4.8.1 and 4.8.2 and are merged into next. At the end, changeset pre exit and a final version produce 5.0.0 with a changelog covering everything since 4.8.0. The guard step in CI stopped one early attempt to merge next into main before exiting pre mode.

Prevention and guardrails

  • Run pre mode on a dedicated branch, never on main.
  • Guard CI so pre.json cannot exist on the stable branch.
  • Exit with changeset pre exit, never by deleting pre.json.
  • Verify tags after each publish with npm dist-tag ls.

Frequently Asked Questions

Can I change the pre tag mid-cycle? Exit and re-enter pre mode with a new tag (for example, moving from beta to rc). Versions continue from the current prerelease numbers.

Does pre mode work with fixed version groups? Yes. All packages in a fixed group get the same prerelease version, as they would in stable releases.

What if I need a hotfix on the stable line during pre mode? Release it from main as usual — pre mode lives only on the next branch — then merge main into next.

How do consumers upgrade from a prerelease to the final version? A range such as ^5.0.0-next.3 already includes 5.0.0 and later 5.x versions, so a normal update picks up the stable release. Consumers who pinned an exact prerelease must change the pin.

Can I publish a prerelease of just one package? Pre mode applies to the whole repository. For a one-off prerelease of a single package, use a snapshot release instead, or version that package manually with a prerelease identifier and publish it with an explicit tag.

What happens to pre.json after exiting? changeset pre exit marks the mode as exited; the next changeset version removes the file and the consumed changesets as it produces stable versions. Commit the result as usual.

Related

Release Channels and Dist-Tags