Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Semantic Release Automated Versioning

By Kokil Thapa | Last reviewed: September 2026

Manual version bumps waste time and break trust. One developer tags v2.3.1 while another ships 2.4.0 without a changelog entry. Semantic Release Automated Versioning fixes that by reading your commit history and letting CI publish the next semver tag, release notes, and package artefact. If you already run automated checks in CI, this is the natural next step for libraries, CLI tools, and frontend packages your team ships repeatedly.

What is Semantic Release Automated Versioning and how does it work?

Semantic Release is an npm CLI that automates the release workflow for Node.js packages. It does not guess versions from file diffs. It parses commit messages against the Conventional Commits specification and applies Semantic Versioning rules to decide whether the next release is a patch, minor, or major increment.

The tool runs only on protected branches—typically main or master. Feature branches get no release. That design keeps nightly experiments from publishing broken packages to npm.

On a real client project, I treat Semantic Release Automated Versioning as a contract between developers and CI. Developers write structured commits. CI owns tags, changelogs, and registry uploads. Nobody edits package.json version fields by hand on release day.

Semantic Release Automated Versioning FlowGit Pushmain branchCI Runnertests passAnalyzeConventional CommitsVersionsemver bumpChangelogCHANGELOG.mdGit Tagv2.4.0Publishnpm registryNo releasable commits = CI exits cleanly, no tagdocs: and chore: commits alone do not trigger a release
Semantic Release Automated Versioning runs entirely inside CI after tests pass on the main branch

The default plugin chain handles most teams without custom code:

  • @semantic-release/commit-analyzer — reads commits since the last tag and picks the semver level.
  • @semantic-release/release-notes-generator — builds human-readable release notes.
  • @semantic-release/changelog — commits an updated CHANGELOG.md back to the repo.
  • @semantic-release/git — commits version bumps in tracked files like package.json.
  • @semantic-release/npm — publishes to the npm registry.
  • @semantic-release/github or gitlab — creates a release on your forge.

Laravel and PHP projects do not use semantic-release for the framework itself. You can still adopt the same commit discipline for shared npm assets—Vite 8.x frontends, design tokens, or internal CLI tools. Your PHP app versioning stays separate, often tied to API route versioning rather than npm semver.

How do you set up semantic-release in a Node.js CI pipeline?

Start with a package that actually publishes to npm or GitHub Releases. Internal Laravel apps deployed via Deployer rarely need semantic-release unless you extract a reusable library.

Install dependencies

Use Node.js 26 LTS and npm 12 on your CI runner. Pin them in your pipeline config so local and CI environments match.

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

Create the release config

Add release.config.js at the project root:

/** @type {import('semantic-release').GlobalConfig} */
export default {
  branches: ['main'],
  plugins: [
    '@semantic-release/commit-analyzer',
    '@semantic-release/release-notes-generator',
    ['@semantic-release/changelog', { changelogFile: 'CHANGELOG.md' }],
    '@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'
  ]
};

The [skip ci] token in the release commit message prevents infinite CI loops. GitLab CI and GitHub Actions both honour common skip patterns when configured correctly.

Wire GitLab CI

I've used this pattern on sister sites sharing a Deployer 7 + GitLab CI pipeline. The release job runs only after lint and test jobs succeed on main:

stages:
  - test
  - release

test:
  stage: test
  image: node:26
  script:
    - npm ci
    - npm test

release:
  stage: release
  image: node:26
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - npm ci
    - npx semantic-release
  variables:
    GITLAB_TOKEN: $GL_TOKEN
    NPM_TOKEN: $NPM_TOKEN

Store GL_TOKEN and NPM_TOKEN as masked CI variables. The GitLab token needs write_repository scope so semantic-release can push tags and changelog commits. The npm token needs publish rights for your scoped package.

Verify locally before merging

Run a dry run to preview the next version without publishing:

npx semantic-release --dry-run --no-ci

The dry-run output lists which commits trigger a release and what version number CI would assign. Fix commit message formatting before merging if the analyser reports no releasable commits when you expected one.

Commit Type to Semver BumpConventional Commitfix:PATCH 1.0.0 to 1.0.1feat:MINOR 1.0.0 to 1.1.0BREAKINGMAJOR 1.0.0 to 2.0.0docs:no releasechore:no releaseperf:PATCH releaseFooter BREAKING CHANGE: always triggers major bump
Semantic-release maps Conventional Commit prefixes to patch, minor, or major semver increments

What commit message format does semantic-release require?

Semantic-release depends entirely on commit message structure. A free-form message like "fixed login bug" produces no release even when the code change is critical. Train your team on Conventional Commits before enabling automated publishing.

Valid prefixes include feat:, fix:, perf:, refactor:, docs:, chore:, and test:. Only certain types trigger version bumps. Documentation and chore commits update the repo but skip releases unless they include a breaking-change footer.

Examples that semantic-release understands:

feat(auth): add OAuth2 password grant support

fix(cart): prevent double-charge on retry

feat(api)!: remove v1 endpoints

BREAKING CHANGE: v1 routes deleted; migrate to /api/v2

The exclamation mark after the type scope (feat(api)!:) is shorthand for a breaking change. The explicit BREAKING CHANGE: footer works inside any commit type and always forces a major bump.

Enforce format at commit time with commitlint and Husky hooks. Without enforcement, one sloppy merge commit blocks an expected release and nobody knows why until CI logs show "no releasable commits."

For mixed stacks—Laravel 13.x backend plus a Vite frontend—keep commit scopes clear. Use feat(ui): for frontend features and fix(api): for backend fixes if both live in one monorepo with shared semantic-release config.

How does semantic-release compare to manual versioning?

Teams without automation rely on a release manager to bump package.json, write changelog entries, create Git tags, and run npm publish. That works for quarterly releases. It falls apart when you ship weekly fixes to a shared internal library used across multiple client projects.

CriteriaManual versioningSemantic Release Automated Versioning
Version accuracyDepends on human judgment; easy to ship minor changes as patchesDeterministic rules from commit types; same input always yields same semver
Changelog qualityOften stale, copied from memory, or skipped entirelyGenerated from commit messages on every release
Release speedBlocks on one person with publish credentialsRuns automatically after CI green on main
Audit trailTags may not match changelog entriesTag, changelog commit, and registry version stay aligned
Setup costZero tooling; high ongoing labourOne-time CI and commitlint setup; low ongoing cost
Best fitPrivate apps deployed via SSH/Deployer with no npm packagePublished npm packages, shared UI libraries, open-source tools

On production Laravel applications I deploy with Deployer 7, version numbers often live in environment config rather than npm. Semantic-release shines when you maintain a package other teams install—not when you rsync a Blade app to an Ubuntu server. Match the tool to the artefact.

If you need help wiring CI for a mixed PHP and Node stack, support and maintenance services often cover pipeline hardening alongside application fixes.

Manual vs Automated VersioningBeforeAfterDeveloper edits package.jsonCI reads commit historyManual CHANGELOG editAuto-generated notesForgotten git tagTag matches npm versionFriday deploy panicMerge to main, CI shipsSemantic Release Automated Versioning removes human steps
Automated semantic release eliminates manual version edits, tag mismatches, and release-day bottlenecks

What are common semantic-release mistakes in production CI/CD?

Most failures I see are configuration problems, not bugs in semantic-release itself. The tool fails loudly when credentials or branch rules are wrong. Learn these patterns before your first production release.

Infinite CI loops

When semantic-release commits a changelog back to main, that push retriggers CI. Without [skip ci] in the release commit message, your pipeline runs release twice. Worse, a misconfigured loop burns runner minutes every night.

Missing npm provenance or 2FA

npm requires automation tokens for CI publishes when 2FA is enabled on the account. Legacy passwords fail silently in some setups. Create a granular access token with publish-only scope and store it as a masked variable.

Squash merges hide commit history

GitHub squash merges collapse PR commits into one message. If the squash title ignores Conventional Commits format, semantic-release sees one useless commit like "Update stuff" and skips the release. Enforce PR title format or rebase-merge instead.

Monorepo complexity without a monorepo plugin

Standard semantic-release assumes one package at the repo root. Multiple packages need @semantic-release/monorepo or independent per-package configs. I've seen teams publish the wrong package version because paths were not scoped correctly.

Expecting PHP Composer releases

Semantic-release targets npm by default. PHP libraries on Packagist need different tooling—often manual tags or a custom CI script. Do not force semantic-release onto a Composer-only repo without the community PHP plugins and clear justification.

Production CI Release StagesLinteslintTestunit + e2eBuildVite 8.xReleasesemantic-releaseRequired CI secretsNPM_TOKEN + GITLAB_TOKEN with write_repositoryBranch: main onlyfeature branches skipProtected branchno direct push
Run semantic-release only after lint, test, and build stages pass on a protected main branch

Validate your release config JSON with the JSON formatter tool before committing plugin arrays. A trailing comma in a copied config breaks the release job at the worst moment.

Advanced: monorepos and pre-releases

Use the branches array for beta and alpha channels:

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

Commits on beta publish versions like 1.2.0-beta.1. Consumers opt in via npm dist-tags. This pattern suits shared component libraries shipped alongside e-commerce frontends where staging needs npm packages before production promotion.

For API versioning at the HTTP layer—not npm semver—read API versioning strategies compared. Semantic-release and URL-based API versions solve different problems and often coexist in the same organisation.

Key Takeaways

  • Semantic Release Automated Versioning converts Conventional Commits into semver tags, changelogs, and npm publishes inside CI—no manual package.json edits.
  • Install semantic-release with changelog, git, and npm plugins; run it only on protected main after tests pass.
  • Enforce commit format with commitlint—squash merges and vague PR titles are the top reason releases silently skip.
  • Use dry-run mode locally to preview the next version before merging breaking changes.
  • Reserve semantic-release for published npm packages; Deployer-managed Laravel apps need different release mechanics.
  • Store NPM_TOKEN and forge tokens as masked CI variables and include [skip ci] in release commit messages to prevent pipeline loops.

People Also Ask

Does semantic-release work with GitHub Actions?

Yes. Replace GitLab variables with GITHUB_TOKEN or a personal access token with repo scope. The official @semantic-release/github plugin creates releases and uploads assets. The commit-analyzer and npm plugins work identically across CI platforms.

Can semantic-release version private packages?

It publishes to private npm registries the same way as public ones. Provide an automation token with publish rights to your scoped package. Self-hosted GitLab npm registries and GitHub Packages both integrate through the npm plugin with adjusted registry URLs.

What happens if no commits warrant a release?

CI exits with code zero and logs "no releasable commits." No tag is created and nothing publishes to npm. This is normal when a sprint only includes documentation and chore commits.

Is semantic-release the same as conventional-changelog?

Related but different. conventional-changelog generates changelogs from commits without publishing. semantic-release orchestrates the full release—including version calculation, git tags, registry publish, and forge releases—in one automated pipeline.

Ship reliable releases without manual version drift

Semantic Release Automated Versioning pays off when your team ships shared packages weekly and needs every tag to match a changelog entry and npm artefact. Start with commitlint, add a dry-run job to CI, then enable publishing on main once credentials and branch protection are solid. The setup takes an afternoon; the saved release meetings last for years.

If you want help connecting semantic-release to a broader GitLab CI pipeline—or separating npm library versioning from your Laravel deploy workflow—reach out for a consultation. You can also review how similar automation was applied on the Adventure Third Pole Trek booking platform or explore custom software development for mixed PHP and Node stacks. For related reading, see PHP Rector for automated refactoring, automated server backups, and model versioning and registries. Visit the home page, about page, services overview, portfolio, blog, or Linux system administration for more resources. Need API work alongside frontend packages? See API development services and enterprise application development.

Frequently Asked Questions

Semantic Release Automated Versioning is a CI-driven workflow where the semantic-release npm CLI reads Conventional Commits on your main branch, calculates the next semver bump, generates release notes and a changelog, creates a Git tag, and publishes to npm or GitHub Releases—without anyone hand-editing package.json on release day.

It does not guess from file diffs. The @semantic-release/commit-analyzer plugin parses commit messages since the last tag against the Conventional Commits specification and applies Semantic Versioning rules. Feature commits typically trigger minor bumps, fixes trigger patches, and breaking changes—marked with feat(api)!: or a BREAKING CHANGE: footer—force a major increment. The same commit history always yields the same version.

Install semantic-release with @semantic-release/changelog, @semantic-release/git, and @semantic-release/github as dev dependencies on Node.js 26 LTS with npm 12. Add release.config.js at the project root with your plugin chain and branches: ['main']. Wire a release job in GitLab CI or GitHub Actions that runs only on main after lint and test jobs pass. Store NPM_TOKEN and a forge token with write_repository scope as masked CI variables, then run npx semantic-release in the release stage.

Semantic-release depends entirely on Conventional Commits structure. Valid prefixes include feat:, fix:, perf:, refactor:, docs:, chore:, and test:, but only certain types trigger version bumps. A message like "fixed login bug" produces no release. Breaking changes need feat(api)!: shorthand or an explicit BREAKING CHANGE: footer. Enforce format at commit time with commitlint and Husky hooks, or one sloppy merge blocks an expected release until CI logs show no releasable commits.

Manual versioning depends on a release manager bumping package.json, writing changelogs from memory, tagging, and running npm publish—workable for quarterly releases but brittle when you ship weekly fixes to shared libraries. Semantic Release Automated Versioning makes version accuracy deterministic, generates changelogs from commits every time, runs automatically after CI passes on main, and keeps tags, changelog commits, and registry versions aligned. Setup cost is higher once; ongoing labour drops sharply.

The default chain most teams need without custom code includes @semantic-release/commit-analyzer to pick the semver level, @semantic-release/release-notes-generator for human-readable notes, @semantic-release/changelog to commit CHANGELOG.md, @semantic-release/git to commit version bumps in package.json and lock files, @semantic-release/npm to publish to the registry, and @semantic-release/github or gitlab to create a forge release. Each plugin handles one step so the full release stays automated inside CI.

Most failures are configuration problems, not semantic-release bugs. Watch for infinite CI loops when release commits lack [skip ci], missing npm automation tokens when 2FA is enabled, squash merges that collapse PR history into non-Conventional titles like "Update stuff", monorepos running standard config without @semantic-release/monorepo, and forcing semantic-release onto Composer-only PHP repos without appropriate plugins. Always run release only after lint, test, and build stages pass on protected main.

When @semantic-release/git commits a changelog and version bump back to main, that push retriggers CI. Without [skip ci] in the release commit message template—chore(release): ${nextRelease.version} [skip ci]—your pipeline runs the release job again and can burn runner minutes nightly. GitLab CI and GitHub Actions both honour common skip patterns when configured correctly. Validate your release.config.js before committing; a trailing comma in a copied plugin array breaks the release job at the worst moment.

Yes. Replace GitLab variables with GITHUB_TOKEN or a personal access token with repo scope. The official @semantic-release/github plugin creates releases and uploads assets. The commit-analyzer, changelog, git, and npm plugins work identically across CI platforms. Wire the release job to run only on main after tests pass, store NPM_TOKEN as a masked variable, and include [skip ci] in release commit messages the same way you would on GitLab CI.

CI exits with code zero and logs "no releasable commits." No tag is created and nothing publishes to npm. This is normal when a sprint only includes documentation and chore commits that skip version bumps unless they carry a breaking-change footer.

Related but different. conventional-changelog generates changelogs from commits without publishing. semantic-release orchestrates the full release—version calculation, git tags, registry publish, and forge releases—in one automated pipeline.

It publishes to private npm registries the same way as public ones. Provide an automation token with publish rights to your scoped package when 2FA is enabled on the account—legacy passwords fail silently in some setups. Self-hosted GitLab npm registries and GitHub Packages both integrate through the @semantic-release/npm plugin with adjusted registry URLs. Store the token as a masked CI variable alongside your forge token.

Laravel and PHP projects do not use semantic-release for the framework itself. PHP app versioning stays separate, often tied to API route versioning rather than npm semver. You can still adopt the same Conventional Commits discipline for shared npm assets—Vite 8.x frontends, design tokens, or internal CLI tools in a Laravel 13.x stack. PHP libraries on Packagist need different tooling, often manual tags or custom CI scripts, not the default npm-focused workflow.

Reserve it for published npm packages, shared UI libraries, and open-source tools—not private Laravel apps deployed via Deployer 7 over SSH to an Ubuntu server. Internal apps where version numbers live in environment config rather than package.json rarely benefit. Match the tool to the artefact. If nothing publishes to npm or GitHub Releases, manual deploy versioning or API route versioning solves a different problem with less pipeline complexity.

Run a dry run locally to preview the next version without publishing: npx semantic-release --dry-run --no-ci. The output lists which commits trigger a release and what version number CI would assign. Fix commit message formatting before merging if the analyser reports no releasable commits when you expected one. Adding a dry-run job to CI before enabling publishing on main catches formatting problems early. Full setup typically takes an afternoon once credentials and branch protection are solid.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: