Two astronauts kneel beside a glass greenhouse dome on a red Martian plain, with glowing towers and a rover behind them.
Guide

semantic-release: How It Works and How to Set It Up with GitHub Actions

Felix Macx · · 15 min read

semantic-release automates versioning and package publishing from commit messages. This guide configures a JavaScript package to release from GitHub Actions, with npm trusted publishing and fixes for common setup errors.

You’ll need Conventional Commits on your release branch. If you squash-merge, check the final commit messages too; the commitlint guide covers enforcing the convention.

Jump to: GitHub Actions setup · npm authentication · Prereleases · Troubleshooting

What semantic-release does

On every CI run on a release branch, semantic-release does the following, in order:

  1. Finds the last release by reading the Git tags in the branch’s history (v1.4.2, by default).
  2. Analyses the commits since then. A breaking change means a major release, a feat means minor, and a fix or perf means patch. Anything else means no release.
  3. Generates release notes from those commits.
  4. Prepares the package, including setting the version in package.json with the npm plugin.
  5. Tags the commit and pushes the tag and Git notes.
  6. Publishes the package to npm and creates a GitHub Release with the generated notes, using the default publishing plugins.
  7. Reports success, including comments on the issues and pull requests that shipped in the release.
Commits since the last tag
          ↓
Decide version → Generate notes → Prepare package
                                          ↓
                                  Push tag and Git notes
                                          ↓
                              Publish npm + GitHub Release
                                          ↓
                                   Report success

The tag is pushed before publishing. A failed npm publish can therefore leave a release tag on the remote; check the registry and CI logs before deciding how to recover.

The defaults are four plugins: @semantic-release/commit-analyzer, @semantic-release/release-notes-generator, @semantic-release/npm, and @semantic-release/github. Everything else, including writing a CHANGELOG.md, is opt-in.

Three design decisions surprise people, so it’s worth knowing them before you start:

  • There is no human gate. If the commits warrant a release, the release happens. The maintainers’ position is that main should always be releasable, and if it isn’t, the problem is the branch, not the tool.
  • Nothing is committed back by default. The version lives in Git tags and in the published package. Your repository’s package.json keeps a placeholder version forever, and that’s intended.
  • The first release is 1.0.0. semantic-release doesn’t support starting at 0.0.1 or working in the 0.x range. If you’re not ready for 1.0, the documented answer is prereleases on a beta branch.

Step-by-step setup with GitHub Actions

This is the path for a JavaScript package published to npm from GitHub Actions. Non-npm projects follow the same steps with a different plugin list, covered further down.

Step 1: Check the prerequisites

  • A supported Node version in the release job. semantic-release v25.0.9 requires ^22.14.0 || >=24.10.0: Node 22.14+ within the 22.x line, or Node 24.10+. Node 23 and Node 24.0–24.9 do not satisfy that range. See the versioned package requirements. The v26 betas raise it again, to ^22.22.2 or >=24.15. Your app can target older Node; only the job that runs semantic-release needs a recent one.
  • Conventional commit messages on the release branch. If you squash-merge, the PR title usually becomes the commit message, so that’s what has to be valid.
  • A placeholder version in package.json. The docs recommend 0.0.0-development or 0.0.0-semantically-released so nobody mistakes it for the real version.
  • A tag for your last release, if you’ve released before. semantic-release starts from the latest version tag in the branch history. If you published 1.1.0 by hand, make sure the last commit of that release is on main and tagged v1.1.0. Otherwise the first automated release will be 1.0.0 and will contain every commit you’ve ever made.

Step 2: Install it

npm install --save-dev semantic-release

That’s the only package you need for the default flow. The four default plugins ship inside it, and the plugin READMEs explicitly warn against adding them as direct dependencies too, because pinned copies can conflict when you upgrade.

Step 3: Add the configuration

Create .releaserc.json at the root of the repository:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/npm",
    "@semantic-release/github"
  ]
}

Listing the default plugins isn’t strictly required, but it makes the file the single place you look when you add one later. Configuration can also live in a release key in package.json or in a release.config.mjs file. Pick one; semantic-release reads the first it finds.

If your team follows the Conventional Commits spec to the letter, including feat!: for breaking changes, switch the parser preset from the default angular to conventionalcommits so the analyser reads commits the same way your linter checks them:

npm install --save-dev conventional-changelog-conventionalcommits
{
  "branches": ["main"],
  "plugins": [
    ["@semantic-release/commit-analyzer", { "preset": "conventionalcommits" }],
    ["@semantic-release/release-notes-generator", { "preset": "conventionalcommits" }],
    "@semantic-release/npm",
    "@semantic-release/github"
  ]
}

Set the preset on both plugins. If they disagree, the version and the release notes are computed from different readings of the same commits.

Step 4: Add the workflow

Create .github/workflows/release.yml. This is the shape the official GitHub Actions recipe recommends: tests run first, and the release job only starts if they pass.

name: Release

on:
  push:
    branches:
      - main

permissions:
  contents: read

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: "lts/*"
      - run: npm clean-install
      - run: npm test

  release:
    needs: verify
    runs-on: ubuntu-latest
    permissions:
      contents: write       # create the tag and the GitHub Release
      issues: write         # comment on issues fixed in the release
      pull-requests: write  # comment on PRs merged in the release
      id-token: write       # npm trusted publishing and provenance
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: "lts/*"
      - run: npm clean-install
      - run: npm audit signatures
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release

Five lines in that file are responsible for most failed first runs:

  • fetch-depth: 0. actions/checkout defaults to a shallow clone of one commit. semantic-release needs the full history and the tags to find the last release and the commits since, and the official readiness checklist lists this setting first.
  • GITHUB_TOKEN in env. The built-in token exists in every workflow, but semantic-release only sees it if you pass it as an environment variable. Leave it out and the GitHub plugin fails with ENOGHTOKEN.
  • The permissions block on the release job. The top-level contents: read is the safe default for everything else; the release job needs write access to create tags and releases and to leave the “released in” comments.
  • No registry-url in setup-node. It looks harmless and appears in many npm publishing examples, but it writes an .npmrc that conflicts with semantic-release’s own npm authentication and produces EINVALIDNPMTOKEN even with a valid token. The docs call this out in bold. If you need a custom registry, put it in a committed .npmrc instead.
  • id-token: write. Required for npm trusted publishing, which is the next step.

Step 5: Connect npm with trusted publishing

Trusted publishing lets npm accept a publish from a specific GitHub workflow using a short-lived OIDC credential, so there’s no long-lived npm token sitting in your repository secrets. It’s now the recommended path in the semantic-release docs, and npm has been tightening the rules on long-lived tokens since late 2025, which makes the old NPM_TOKEN approach more fragile every year. As a bonus, packages published this way get npm provenance automatically.

On npmjs.com, open the package’s settings, add a Trusted Publisher for GitHub Actions, and enter your GitHub organisation or user, the repository, and the workflow filename (release.yml above). These setup details matter:

  • Use a GitHub-hosted runner. npm trusted publishing does not currently support self-hosted runners. The ubuntu-latest runner above is supported.
  • Complete the first successful publish within two days of configuring the trusted publisher. Otherwise the unvalidated configuration expires and must be recreated. See the npm trusted publishing documentation.
  • Name the workflow that triggers the run. If your release job lives in a reusable workflow, npm needs the caller workflow (the one triggered by push), not the reusable one.
  • The setting lives on the package’s page, so a package that has never been published needs its first version published another way before you can switch it over.

If trusted publishing isn’t an option, create a granular npm access token with write access to the package and Bypass 2FA enabled for unattended CI publishing, where your package policy allows it. Store it as a repository secret called NPM_TOKEN and add it to the release step’s env next to GITHUB_TOKEN. Plan to rotate it before expiry. Classic tokens, including automation tokens, have been revoked. You can drop id-token: write if you are not generating provenance or using another OIDC feature.

Step 6: Preview, then run the first release

Before pushing, you can preview what semantic-release would do from your own machine. Outside CI, dry-run mode is on by default, so it prints the next version and the release notes without publishing:

GITHUB_TOKEN="YOUR_GITHUB_TOKEN" npx semantic-release --dry-run --no-ci

Replace YOUR_GITHUB_TOKEN with a token authorized for this repository, or use an already configured GITHUB_TOKEN environment variable and run only npx semantic-release --dry-run --no-ci. The run still checks that it could push to the repository, which is why it needs a token. If it says the current branch isn’t a release branch, run it from an up-to-date main.

Then merge a fix: or feat: commit to main. When the workflow finishes, you should see a new tag (v1.0.0 on a first release), a published version on npm, a GitHub Release with generated notes, and a “This PR is included in version 1.0.0” comment on the merged pull request. If you see none of that, jump to troubleshooting.

How semantic-release decides the version

The commit analyser applies these default rules to every commit since the last release, and the highest one wins:

Commit Example Release
Breaking change feat(api)!: drop v1 endpoints or a BREAKING CHANGE: footer Major
feat feat(reports): add CSV export Minor
fix fix(auth): refresh expired sessions Patch
perf perf(search): cache index lookups Patch
Anything else chore, docs, refactor, test, ci, build, style None

A few consequences follow:

  • Ten chore: commits release nothing. That’s working as designed, not a bug. Use fix: or feat: for anything a user should get.
  • You can change the rules. The commit analyser’s releaseRules option maps any type or scope to a release, for example { "type": "refactor", "release": "patch" } or { "scope": "no-release", "release": false }.
  • You can exclude a commit. A message containing [skip release] or [release skip] is ignored by the analysis.
  • You can’t pick a version by hand. There’s no equivalent of Release Please’s Release-As footer; the docs say outright that choosing a specific version defeats the point of the tool.
  • The BREAKING CHANGE: footer has to be exact. With the angular preset, the keyword needs the colon and has to be in the footer, after a blank line. A BREAKING CHANGE mentioned in the middle of the body doesn’t count.

If the numbers themselves are the confusing part, Semantic Versioning Explained covers what each bump promises the people who depend on your package.

Plugins and the release steps

Every semantic-release run goes through a fixed sequence of steps: verifyConditions, analyzeCommits, verifyRelease, generateNotes, prepare, publish, addChannel, success, and fail. A plugin implements one or more of them, and plugins run in the order you list them, step by step. That ordering is why the plugin list sometimes matters.

The plugins you’re most likely to use:

Plugin What it does In the defaults?
@semantic-release/commit-analyzer Decides the release type from commits Yes
@semantic-release/release-notes-generator Writes the release notes Yes
@semantic-release/npm Sets the version and publishes to npm Yes
@semantic-release/github GitHub Release, issue and PR comments, a failure issue Yes
@semantic-release/changelog Writes the notes into CHANGELOG.md No
@semantic-release/git Commits changed files back to the repo No
@semantic-release/exec Runs your own shell commands at any step No
@semantic-release/gitlab The GitLab equivalent of the GitHub plugin No

Two GitHub plugin behaviours are worth knowing on day one. It comments on every issue and pull request included in a release and labels them released. And when a release fails, it opens an issue titled “The automated release is failing” with the error and a suggested fix, then closes it when a release succeeds again. Both can be turned off or reworded in the plugin’s options (successComment, failComment, releasedLabels, and so on).

Committing CHANGELOG.md and package.json back (and why the docs advise against it)

Many teams want the version in package.json and a CHANGELOG.md in the repository to stay current. semantic-release can do it with two additional plugins. Install them first:

npm install --save-dev @semantic-release/changelog @semantic-release/git

Then add them to the configuration in this order:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    ["@semantic-release/git", {
      "assets": ["CHANGELOG.md", "package.json", "package-lock.json"],
      "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
    }],
    "@semantic-release/github"
  ]
}

changelog has to come before git, and so does npm, because git commits whatever the earlier plugins changed in the prepare step. The [skip ci] in the commit message stops the release commit from triggering another workflow run.

The maintainers’ FAQ is unusually direct that they recommend against this. Pushing a commit from CI means the release identity has to get past your branch protection rules and any commit hooks (a husky and commitlint setup will happily reject the release commit). The built-in GITHUB_TOKEN can’t push to a protected branch, and the docs warn against working around that with a personal access token, because any workflow on any branch could read it. Their recommended route, if you need the commit, is a GitHub App token created with actions/create-github-app-token and passed both to actions/checkout and as GITHUB_TOKEN.

Their other point is that the GitHub Release already holds the same notes. A CHANGELOG.md that only links to your GitHub Releases page is a legitimate middle ground, and it means nothing has to be committed at release time.

Branches, prereleases, and maintenance releases

Without a branches setting, semantic-release uses this default:

["+([0-9])?(.{+([0-9]),x}).x", "master", "main", "next", "next-major",
 {"name": "beta", "prerelease": true}, {"name": "alpha", "prerelease": true}]

In practice that means:

  • main or master publishes regular releases to the default channel (latest on npm).
  • next and next-major, if they exist, publish regular versions ahead of main to npm dist-tags of the same name.
  • beta and alpha publish prereleases like 2.0.0-beta.1 to the beta and alpha dist-tags. Users only get them by asking for them (npm install your-package@beta).
  • Maintenance branches named like 1.x or 1.2.x publish fixes to older major or minor lines without touching latest.

A common setup for a library heading towards a breaking release:

{
  "branches": [
    "main",
    { "name": "beta", "prerelease": true }
  ]
}

Work on beta releases 2.0.0-beta.1, 2.0.0-beta.2, and so on. When beta merges into main, semantic-release publishes 2.0.0 to latest. Remember to run the workflow on those branches too, by adding them under on.push.branches.

semantic-release records which channel each version went to in Git notes as well as tags. That’s why force-pushing a release branch can make it “forget” releases: the rewritten commits lose their tags and notes. The troubleshooting docs have a recovery procedure; the better rule is not to force-push release branches.

Monorepos

semantic-release has no built-in monorepo support. It was designed around one package per repository, and the community fills the gap:

  • semantic-release-monorepo wraps the analyser and notes generator so each package only considers commits that touched files under its own directory. You run semantic-release once per package, usually through your workspace tool, and tags get a package prefix.
  • multi-semantic-release (and its maintained forks) runs semantic-release across all workspace packages in one pass and updates dependent packages’ version ranges.

Both work and both are widely used, but they’re community projects layered over a core that doesn’t know about them, and that shows up as edge cases around tag formats and cross-package bumps. If a monorepo is the main reason you’re evaluating release tooling, two tools treat it as a first-class case: Changesets, the default in JavaScript workspaces, and Release Please, whose manifest configuration and workspace plugins are covered in the Release Please monorepo section.

Using semantic-release outside JavaScript

semantic-release is a Node CLI, but it can release anything. For a project without a package.json, drop the npm plugin and use @semantic-release/exec to run your own version and publish commands. Install the CLI and exec plugin in your release tooling environment:

npm install --save-dev semantic-release @semantic-release/exec

Then configure your own scripts:

{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    ["@semantic-release/exec", {
      "prepareCmd": "./scripts/set-version.sh ${nextRelease.version}",
      "publishCmd": "./scripts/publish.sh"
    }],
    "@semantic-release/github"
  ]
}

Run it with npx semantic-release in a job that has Node available. Community plugins exist for many ecosystems (Docker images, Maven, Gradle, Helm charts, Cargo, and more); check the official plugins list before writing shell scripts.

Python Semantic Release is a different project. It shares the idea and the name but not the code or the config. It’s a Python package configured in pyproject.toml, and none of the plugin or .releaserc advice in this post applies to it. If you landed here for Python, use its own documentation.

Troubleshooting: the errors you’ll actually hit

“There are no relevant changes, so no new version is released.”

semantic-release ran correctly and found nothing that warrants a release. Check, in order:

  1. The commit types. Only feat, fix, perf, and breaking changes release by default. A run of chore: and docs: commits releases nothing.
  2. The squash-merge message. If GitHub used a PR title like “Update reports page” as the commit, the analyser ignores it. Lint PR titles, as covered in the commitlint guide.
  3. The preset. A feat!: breaking change, or a type your linter accepts that the angular preset doesn’t parse, can slip through. Switch both plugins to the conventionalcommits preset, as in Step 3.

“This test run was triggered on the branch feature-x, while semantic-release is configured to only publish from main”

Not an error. semantic-release ran on a branch that isn’t in branches, and stopped. It’s expected on feature branches and pull requests. If you see it on the branch you meant to release from, the branch name in .releaserc.json doesn’t match (a classic is master versus main).

ENOGHTOKEN: No GitHub token specified

The GitHub plugin couldn’t find a token. Pass it in the release step’s env as GITHUB_TOKEN (or GH_TOKEN). Having it available in the workflow isn’t enough; it has to be in the environment of the step that runs semantic-release.

EGITNOPERMISSION, or a push rejected by branch protection

semantic-release checks that it can push to the repository before it does anything else, because it has to push the tag. Check that the release job has contents: write. If you’re also committing files back with @semantic-release/git to a protected branch, the built-in token isn’t allowed through. Use a GitHub App token that’s allowed to bypass the protection, as described above.

EINVALIDNPMTOKEN or ENEEDAUTH

npm authentication failed. The usual causes:

  • registry-url is set in actions/setup-node. Remove it. This is the most common cause, and it fails even with a valid token.
  • The token expired or has the wrong type. npm’s 2025 changes shortened the life of write tokens, so a token that worked last quarter may simply be dead. Moving to trusted publishing removes this failure entirely.
  • Trusted publishing is half set up. Check that id-token: write is on the release job and that the workflow filename on npmjs.com matches the workflow that triggers the run.

npm ERR! code E403: You do not have permission to publish

Either the authenticated npm identity isn’t a maintainer of the package, or the package name already belongs to someone else. Look up an existing package with npm view "your-package-name" name maintainers, replacing the quoted name with yours, and use a scope (@your-org/package) if the name is taken. An E404 means the lookup did not find a visible package; it does not guarantee that npm will allow you to publish that name.

“reference already exists” when pushing a tag

A tag with the version semantic-release wants to create already exists, but not in the current branch’s history. If a real release with that version was published, merge the commits up to it into your release branch. If publishing failed after the tag was pushed, inspect the registry, GitHub Release, and CI logs first: some publishing steps may have succeeded. Only remove a stray tag locally and on the remote when you have confirmed that no release was published under it, then re-run.

The first release is 1.0.0, but you’d already released 3.2.0

semantic-release didn’t find your existing tag in the branch history. Either the last release was never tagged in vX.Y.Z format, the tagged commit isn’t on main, or the checkout was shallow. Tag the last released commit, push the tag, and confirm fetch-depth: 0 is set. If you changed tagFormat, the old tags have to match it.

A Node version error before anything runs

The release job’s Node is older than semantic-release requires. Use node-version: "lts/*" in the release job, even if your test matrix covers older versions.

Is semantic-release the same as semantic versioning?

No, though the names invite the mix-up. Semantic versioning is the specification: MAJOR.MINOR.PATCH, where each number promises something specific about compatibility. semantic-release is a tool that applies the specification automatically, by inferring the bump from commit messages instead of asking a person to pick it. You can follow SemVer without the tool. The tool only works if you follow SemVer, and the semantic versioning guide covers the rules it relies on.

semantic-release vs Release Please

Both read conventional commits and compute a semantic version. The difference is the human gate. semantic-release releases on every qualifying push to the release branch and publishes to npm out of the box. Release Please opens a release pull request with the version and the changelog, and nothing ships until someone merges it; publishing is a step you add yourself.

Pick semantic-release when the branch is the release and you want push-to-publish. Pick Release Please when someone should see the version and the notes before they exist, when your monorepo needs first-class support, or when you ship outside JavaScript and want built-in version-file updaters. The full side-by-side table is in the Release Please guide, and the broader comparison with git-cliff, AI scripts, and managed tools is in how to automate release notes from GitHub commits.

What the release notes look like, and who they’re for

With the default angular preset, a minor release’s GitHub Release body looks like this:

# [1.3.0](https://github.com/acme/widget/compare/v1.2.4...v1.3.0) (2026-10-24)

### Bug Fixes

* **reports:** handle empty filter state on export ([e4f5a6b](https://github.com/acme/widget/commit/e4f5a6b))

### Features

* **reports:** add CSV export to reports table ([#482](https://github.com/acme/widget/issues/482)) ([a1b2c3d](https://github.com/acme/widget/commit/a1b2c3d))

For the people who depend on your package, that’s exactly right: a compare link, changes grouped by type, and a link from every line to the commit. A maintainer upgrading from 1.2.4 knows what moved.

Notice what semantic-release’s design implies, though. The notes are published the moment the commits land, and no person reads them first. With Release Please there’s at least a pull request where someone could reword a line; here there isn’t, by design. That’s fine for a changelog read by developers. It’s a problem the moment the same text is the only place your customers hear about a change, because “reports: handle empty filter state on export” tells a customer nothing about whether their download works now. The changelog vs release notes post covers that gap, and the product changelog post covers what the customer-facing version should say.

Where ReleasePad fits

ReleasePad handles the customer-facing half, and only that. It reads the same merged pull requests and commits semantic-release does, drafts an entry in plain customer language, and waits for you to edit and publish it to a hosted changelog page, an in-app widget, and Markdown, RSS, and JSON feeds. It doesn’t compute versions, publish packages, or create tags, so it doesn’t replace semantic-release or compete with it. semantic-release keeps the registry and the GitHub Release in sync with your commits; the customer entry gets the human review that semantic-release deliberately skips. The GitHub integration page shows how the drafting works.

If everyone who reads your release notes is a developer, you don’t need the second half. semantic-release on its own is a complete answer.

Setup checklist

  1. Make sure commits on main follow Conventional Commits, and lint PR titles if you squash-merge.
  2. Set package.json to 0.0.0-development, and tag your last manual release as vX.Y.Z if you have one.
  3. npm install --save-dev semantic-release.
  4. Add .releaserc.json with your branches and plugins, and set the conventionalcommits preset on both commit plugins if you use ! for breaking changes.
  5. Add the workflow with fetch-depth: 0, the release job’s permissions, GITHUB_TOKEN in env, lts/* Node, and no registry-url.
  6. Configure npm trusted publishing for the triggering workflow, or add an NPM_TOKEN secret.
  7. Preview with npx semantic-release --dry-run --no-ci, then merge a fix: and watch the run.
  8. Decide separately what your customers hear about each release.

Setup details are from the semantic-release documentation (configuration, GitHub Actions recipe, FAQ, troubleshooting, and Node version pages), the commit-analyzer README, and the release history, read on 24 October 2026. semantic-release moves quickly, especially around npm authentication; if a step here doesn’t match what you see, the official docs win.

Further reading

Frequently Asked Questions

What is semantic-release?

semantic-release is an open-source Node.js tool that fully automates versioning and publishing. On every push to a release branch, it reads the conventional commit messages since the last Git tag, works out the next semantic version, generates release notes, prepares the package, pushes the Git tag, publishes the package (to npm by default), and creates a GitHub Release. There is no human step: if the commits say a release is due, it happens. It runs in CI, and plugins extend it to other registries and languages.

How do I set up semantic-release with GitHub Actions?

Install semantic-release as a dev dependency, add a .releaserc.json listing your release branch and plugins, and create a workflow that runs npx semantic-release on push to main. The workflow needs actions/checkout with fetch-depth: 0, a Node version that meets the requirement (^22.14.0 || >=24.10.0 for v25.0.9), GITHUB_TOKEN passed as an environment variable, and contents, issues, and pull-requests write permissions. For npm, add id-token: write and configure trusted publishing on npmjs.com, or fall back to an NPM_TOKEN secret.

Why does semantic-release say there are no relevant changes, so no new version is released?

It means none of the commits since the last release tag matched a release rule. By default only feat (minor), fix and perf (patch), and breaking changes (major) trigger a release, so a run of chore, docs, or refactor commits produces nothing. The other common causes are squash-merge messages that are not valid conventional commits, and a parser preset that does not match how your team writes commits, for example writing feat! with the default angular preset instead of switching to the conventionalcommits preset.

Does semantic-release update package.json and CHANGELOG.md in my repository?

Not by default. The npm plugin sets the version in package.json inside the published package only, and the Git tag records the version for the next run, so nothing is committed back. If you want the version and a CHANGELOG.md committed, add the @semantic-release/changelog and @semantic-release/git plugins. The maintainers recommend against it, because pushing commits during a release has to get past branch protection and commit hooks, which usually means a GitHub App token.

semantic-release github-actions conventional-commits npm versioning automation

Ready to put this into practice?

Your changelog shouldn't be an afterthought.

ReleasePad makes it easy to publish great release notes — from a public changelog page to an in-app widget, GitHub integration, and analytics. Free to get started.

Get started — it's free
Try me now!