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 versionproduces 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 publishpublishes with the pre tag (next) as the dist-tag.- Changeset files are not deleted after versioning;
pre.jsonlists 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.
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.
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.
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.jsoncannot exist on the stable branch. - Exit with
changeset pre exit, never by deletingpre.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 explains channels and tags.
- Automating Releases with Changesets covers the stable Changesets workflow.
- Fixing Changesets Not Detecting a Version Bump helps when versioning produces nothing.
- Maintaining LTS Branches with Backport Releases handles the previous major after the release.