Release automation
Tools that turn merged changes into a release without hand edits: they pick the next version number, write the changelog, tag the commit and publish the package.
4 technologies on this page ↓How it works
Cutting a release is four mechanical steps: choose the next version, record what changed, tag the commit, and publish the artifacts. Release tools do them from information already in the repository. Most follow Semantic Versioning, MAJOR.MINOR.PATCH, where an incompatible API change bumps the major number, a backward-compatible feature the minor, and a backward-compatible fix the patch. Tools differ first in where the bump comes from. semantic-release and release-please read the Conventional Commits since the last version tag: fix: means a patch, feat: a minor, and a ! or a BREAKING CHANGE: footer a major; commits such as chore: release nothing. Changesets instead has each pull request add a small Markdown file whose front matter names the packages and their bump types and whose body becomes the changelog entry, which suits monorepos where one change touches several packages. GoReleaser takes the version from the latest Git tag, and release-it asks for it in a prompt unless run with --ci. Tools also differ in when a release happens. semantic-release runs in CI after every successful build on a release branch and publishes at once. release-please and the Changesets GitHub Action keep a release pull request open that collects the pending version bumps and changelog; merging it is the decision to release, after which the tool tags the commit and creates a GitHub release. Changesets then publishes every package whose version is newer than the registry’s, while release-please leaves publishing to a separate job. Publishing from CI needs registry credentials; npm’s trusted publishing replaces long-lived tokens with short-lived OIDC tokens issued to one authorized workflow, and adds provenance automatically.
A newspaper’s print run. Reporters file stories all day; at press time the edition gets the next issue number and a front-page index of what’s inside, and goes to every newsstand at once. Nobody retypes the issue number by hand, and nothing reaches a stand without being in the index.
Reach for it when
- Libraries, SDKs and CLIs that others pin by version, where a wrong number or a missing changelog entry breaks users
- Monorepos whose packages depend on each other and must be bumped and published together (Changesets)
- Teams that want a person to choose the moment of release but no person to do the steps (a release pull request)
Think twice when
- Applications deployed continuously that nobody installs by version: a commit SHA or build number already identifies each deploy
- Histories without a commit convention: commit-driven tools will skip or misjudge releases; use changeset files or choose versions by hand
- Giving every CI job a long-lived publish token: publish from one restricted job, with trusted publishing where the registry supports it
A real example
A team maintains a TypeScript SDK at version 1.4.2 with release-please. Over two weeks they squash-merge fix: retry uploads on 503 and feat: add progress callback to upload. After each merge release-please updates one open release pull request, now proposing 1.5.0 because a feat means a minor release, with both entries added to CHANGELOG.md and the version in package.json bumped. On Thursday a maintainer reviews and merges it; release-please tags v1.5.0 and creates the GitHub release, and a second workflow job, triggered by that release, runs npm publish with trusted publishing. Nobody typed a version number.
Technologies
Specific products, and what each does differently.
release-itOpen sourceA Node.js command that runs a whole release in one go: pick the next version, bump it, commit, tag, push, create a GitHub or GitLab release and publish to npm, asking before each step unless it runs in CI.
release-plzOpen sourceA release tool for Rust that runs in CI: it keeps a release pull request with the next versions and changelogs of your crates up to date, and when you merge it, publishes the new versions to crates.io, tags them and creates GitHub, GitLab or Gitea releases.| cargo-semver-checks | Open source | Predrag Gruevski and contributors | Apache 2.0 or MIT | 2026-10-24 |
release-it | Open source | Lars Kappert and contributors | MIT | 2026-10-13 |
release-plz | Open source | Marco Ieni and contributors | MIT or Apache 2.0 | 2026-10-24 |
Also used for release automation
Products of other kinds that are often used this way, grouped by what they are.
| Build tools | Open source | Narwhal Technologies, Inc. | MIT (Nx Cloud is a paid service) | 2026-10-13 |
Six ways to cut a release#
The tools agree on Semantic Versioning and differ in where the next version comes from and who decides when to ship.
| Tool | Next version comes from | Release happens when | Publishes packages? |
|---|---|---|---|
| semantic-release | Conventional Commits since the last tag | Every CI run on a release branch that has releasable commits | Yes, through plugins for each package manager |
| release-please | Conventional Commits since the last release | Someone merges the release pull request | No: it tags and creates a GitHub release, and another job publishes |
| Changesets | Changeset files added with each pull request | The version pull request is merged and the publish step runs | Yes: changeset publish publishes each package newer than the one on npm |
| GoReleaser | The latest Git tag, pushed by a person or another tool | A run starts on the tagged commit | Yes: binaries, archives, packages and Docker images to the forge, registries and blob storage |
| release-it | A prompt, or no prompts with --ci | Someone runs it, locally or in CI | Yes: npm, plus a GitHub or GitLab release |
| release-plz | Commits since the version on the cargo registry, raised to a major bump if cargo-semver-checks finds an API break | Someone merges the release pull request | Yes: cargo publish for each crate not yet on the registry, plus tags and forge releases |
- GitHub can also generate release notes for a release, listing its merged pull requests and contributors with a link to the full changelog, without any tool in the repository.
In code · yaml
# .github/workflows/release.yml: release every green push to main
on:
push:
branches: [main]
permissions:
contents: read
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # push the tag, publish the GitHub release
id-token: write # npm trusted publishing (OIDC), no NPM_TOKEN
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # every commit and tag since the last release
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
- run: npm clean-install
- run: npm test
- run: npx semantic-release # version, notes, tag, publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Often confused with
Where it came from2013–2019
Version numbers used to mean whatever each project wanted. Tom Preston-Werner, a cofounder of GitHub, wrote Semantic Versioning to get out of what it calls “dependency hell”: if the numbers follow fixed rules, a user can tell from MAJOR.MINOR.PATCH alone whether an upgrade might break them. Version 2.0.0 of the specification was marked final in June 2013. Olivier Lacan’s Keep a Changelog, first published in May 2014 and at 1.0.0 in June 2017, made the matching case for release notes: “Changelogs are for humans, not machines,” and a dump of commit messages is “full of noise”.
With rules for both, a machine could do the work. semantic-release, started by Stephan Bönnemann and first published to npm in February 2015, runs in CI after every green build on a release branch, reads Angular-style commit messages, picks the version, writes the notes and publishes, so releases are “unromantic and unsentimental”. In 2019 two tools put a person back in at different points. Changesets, first built for monorepos managed with the Bolt tool, has each contributor write the bump and the changelog line as a file in the pull request, and Google’s release-please collects pending changes in a release pull request that a maintainer merges when ready. Both released 1.0.0 on npm within three days of each other in May 2019.
The weak point left was the publish credential: a long-lived registry token stored in CI. npm’s trusted publishing, built on the OpenSSF trusted publishers standard that PyPI and RubyGems also follow, lets a named CI workflow publish with short-lived OIDC tokens that can’t be extracted or reused, and generates provenance automatically; semantic-release now recommends it as the default path.
Why it’s built this way
- Derive the version from commit messages
- If commits already say
fixorfeat, a tool can compute the bump and nobody argues over numbers. semantic-release says this “removes the immediate connection between human emotions and version numbers”. - A release pull request instead of releasing on every merge
- release-please keeps one pull request up to date as work lands, so a maintainer can review the proposed version and changelog and choose the moment to ship by merging it.
- Intent files instead of parsing commits
- Changesets lets contributors “declare how their changes should be released” while the change is fresh, and in a monorepo it bumps the packages that depend on a changed one; commit messages can stay informal.
- Short-lived OIDC tokens for publishing
- A long-lived write token “can be compromised, accidentally exposed in logs, or require manual rotation”. Trusted publishing issues a token per run, valid only for the workflow the package owner authorized.
Timeline
- 2013
Semantic Versioning 2.0.0 is marked final (June 18)
- 2014
Keep a Changelog’s first version is published (May 31)
- 2015
semantic-release is first published to npm (February 2)
- 2017
Keep a Changelog reaches 1.0.0 (June 20)
- 2019
Changesets (May 6) and release-please (May 9) publish 1.0.0 to npm
- Semantic Versioning 2.0.0 (dependency hell; authored by Tom Preston-Werner)
- semver/semver on GitHub: Mark 2.0.0 as RTM (Jun 18, 2013)
- Keep a Changelog 1.1.0 (guiding principles, commit log diffs, version history)
- npm registry: semantic-release package metadata (first published Feb 2, 2015)
- semantic-release on GitHub: first commit by Stephan Bönnemann
- semantic-release README: how it works, CI after every build, “unromantic and unsentimental”
- Changesets docs: Using Changesets (originally designed for Bolt monorepos)
- npm registry: @changesets/cli package metadata (1.0.0 on May 6, 2019)
- npm registry: release-please package metadata (1.0.0 on May 9, 2019)
- release-please README: release pull requests
- npm Docs: Trusted publishing for npm packages (OIDC, OpenSSF standard)
- semantic-release docs: GitHub Actions recipe (trusted publishing recommended)
Sources15
- Semantic Versioning 2.0.0 specification
- Keep a Changelog 1.1.0
- semantic-release README: release steps, commit format and release types
- semantic-release docs: GitHub Actions recipe (workflow, permissions, trusted publishing)
- release-please README: release PRs, releasable units, no publishing to package managers
- Changesets docs: Getting started (changeset files with YAML front matter)
- Changesets docs: Using Changesets (version and publish commands)
- changesets/action README: version pull request, publish, GitHub releases
- GoReleaser docs: How it works (build, publish, announce)
- GoReleaser docs: Quick start (uses the latest Git tag)
- release-it README: interactive and CI modes
- release-plz docs: Introduction (release pull request, Cargo registries, forges)
- release-plz docs: Semver check (major bump on API breaking changes)
- npm Docs: Trusted publishing for npm packages
- GitHub Docs: Automatically generated release notes