Semantic versioning is the industry-standard system for communicating the nature of software changes through version numbers. When it is followed correctly, consumers of your package, API, or service can upgrade with confidence. When it is ignored or misapplied, dependency updates become a guessing game of "will this break me?"
What changed in 2026
- Conventional Commits became the default commit format for projects using automated releases. Tools like semantic-release, Release Please (Google), and Changesets parse commit messages to determine the next version automatically.
- Supply chain security raised the stakes. Organizations now audit every dependency update, making predictable SemVer compliance a security and compliance concern, not just a developer convenience.
- Package registry enforcement tightened. npm, PyPI, and crates.io all provide provenance attestations; version immutability is enforced — you cannot delete a published version, only deprecate it.
- API versioning and library versioning diverged in practice. REST APIs commonly use URL-based versioning (
/v1/, /v2/) that doesn't strictly follow SemVer; library versioning does. Knowing which context you're in matters.
The SemVer contract
MAJOR.MINOR.PATCH[-prerelease][+buildmetadata]
2.14.3-beta.1+sha.abc123
| Component |
Increment when |
Examples |
| MAJOR |
You break backward compatibility |
Removing a method, changing a parameter type, dropping Node 18 support |
| MINOR |
You add functionality in a backward-compatible way |
New optional parameter, new endpoint, new feature with existing API intact |
| PATCH |
You fix bugs in a backward-compatible way |
Fix a crash, correct a calculation, update a doc string |
| Pre-release |
Unstable iteration before a release |
1.0.0-alpha.1, 2.0.0-rc.3 |
The promise: if you depend on ^2.14.0, you will never get a breaking change — only ^2.x.x compatible improvements. This is what package managers rely on when they auto-upgrade patch and minor versions.
Conventional Commits format
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
Common types and their SemVer impact:
feat: add search endpoint → MINOR bump
fix: correct pagination offset → PATCH bump
feat!: remove deprecated /v1 API → MAJOR bump (! = breaking change)
refactor: reorganize auth module → no version bump
docs: update README → no version bump
chore: upgrade dev dependencies → no version bump
A BREAKING CHANGE: footer also triggers a MAJOR bump:
feat: migrate to new authentication scheme
BREAKING CHANGE: the `token` parameter is now required.
Clients that omit it will receive a 401 error.
Automated release with semantic-release
// .releaserc.json
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
["@semantic-release/npm", { "npmPublish": true }],
["@semantic-release/github", { "assets": ["dist/**"] }],
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}]
]
}
Every merge to main triggers semantic-release: it reads the commits since the last tag, determines the version bump, generates the changelog, publishes to npm, and creates a GitHub release — all without human involvement.
How to start
- Write Conventional Commits from your next commit. The format is lightweight; install
commitlint and husky to enforce it in pre-commit hooks.
- Add semantic-release or Release Please to your CI pipeline. Start in dry-run mode (
--dry-run) to see what versions it would produce before you trust it.
- Tag your current state with a meaningful version if you haven't already.
git tag v1.0.0 followed by git push --tags establishes the baseline.
- Write a CHANGELOG.md for the initial release. Automated tools will maintain it from here.
- Set up branch protection to require conventional commit messages on PRs via a CI check — this prevents non-compliant commits that would confuse the version calculator.
Common mistakes
Using MAJOR for every internal refactor. MAJOR means "consumers of your published API must take action to upgrade." Internal restructuring with an unchanged public API is a MINOR or no-bump. Reserve MAJOR for genuine breaking changes.
Ignoring 0.x semantics. Version 0.y.z signals "unstable API" — any MINOR bump can break consumers. Stay at 0.x until you're ready to commit to stability, then move to 1.0.0 deliberately.
Publishing without a changelog. A version bump with no notes forces consumers to read your commits to understand what changed. CHANGELOG.md is a contract, not optional documentation.
Bumping manually when automation exists. Human beings consistently undercount breaking changes or bump MAJOR out of caution when MINOR would do. The commit log doesn't lie; let the tool decide.
Yanking published versions. Most registries don't allow deletion; npm's deprecate and Python's yanked marker are the correct tools. Plan before publishing — once it's out, treat it as permanent.
What to skip
- SemVer for internal packages that only one team uses. The versioning overhead is real. Use a date-based version or a simple monotonic counter internally; reserve SemVer for anything published or consumed outside your team.
- Pre-release versions in production dependencies.
1.0.0-beta.3 in your production package.json means you're accepting instability. Only ship on stable releases.
- Over-engineering the pre-release channel.
alpha, beta, rc are common and sufficient. You don't need canary, next, preview, experimental all at once unless you have a very large consumer base.
FAQ
Does SemVer apply to REST APIs?
Technically no — SemVer was designed for libraries with importable interfaces. REST APIs often use URL versioning (/v1, /v2) instead. But the underlying principle — communicate breaking changes clearly — absolutely applies.
What counts as a breaking change?
Removing a field, changing a field's type, changing a required parameter to an error, dropping support for a runtime version, changing default behavior in a user-visible way. When in doubt, it's breaking.
How do I version a monorepo?
Two approaches: independent versioning per package (each package has its own version — Changesets or semantic-release multi-package mode), or fixed/locked versioning (all packages share one version — Nx release, Lerna fixed mode). Independent is more accurate; fixed is simpler to reason about.
What is lockstep versioning?
Tools like Angular, React (until v18), and the AWS SDK used fixed versioning where all packages release together at the same version. It simplifies compatibility reasoning ("use v17 of everything") at the cost of forcing unnecessary MAJOR bumps for packages with no breaking changes.
Where to go next
Git rebase vs merge in 2026, CI/CD pipeline basics in 2026, and Feature flags guide in 2026.