Semantic versioning in plain English: what each number promises, the 0.x escape hatch, pre-release and build tags, and — the part most guides skip — what each bump type should mean for your changelog.
Every developer has seen 2.14.3 and known, roughly, what it implies. That “roughly” is what semantic versioning (SemVer) formalizes: a three-number scheme where each number answers a specific question about what changed and whether it will break you.
The specification itself is short and worth reading once. This guide is the practical layer around it: how the rules work, where teams misapply them, and what each bump actually obligates you to communicate.
The Three Numbers
A semantic version is MAJOR.MINOR.PATCH — say, 2.14.3. Each position has one job:
| Bump | When | The promise to users | Example |
|---|---|---|---|
MAJOR (2.x.x → 3.0.0) |
Incompatible changes | “Something you rely on changed. Read before upgrading.” | Removed an endpoint, changed a default, renamed a config key |
MINOR (2.14.x → 2.15.0) |
New backward-compatible functionality | “New stuff. Everything old still works.” | Added an endpoint, new optional parameter, new feature |
PATCH (2.14.3 → 2.14.4) |
Backward-compatible bug fixes | “Behaves as documented now. Nothing new.” | Fixed a crash, corrected an off-by-one |
Two rules glue the scheme together. First: a published version never changes. If 2.14.3 has a bug, you ship 2.14.4; you don’t reupload 2.14.3. Second: bumping one number resets the ones after it — 2.14.3 plus a breaking change becomes 3.0.0, not 3.14.3.
The reason this matters is that machines act on it. When a package.json says ^2.14.0, the package manager will auto-accept 2.15.0 but refuse 3.0.0 — the version number is the compatibility contract. Get the bump wrong and you either break users automatically (breaking change shipped as a minor) or hide value from them (feature shipped as a patch).
The 0.x Escape Hatch
Everything above applies once you’ve shipped 1.0.0. Before that, in 0.x.y land, the spec says the public API “should not be considered stable” — anything may change at any time. The ecosystem convention: treat 0.5.0 → 0.6.0 the way you’d treat a major bump.
The practical guidance hiding in that rule: 1.0.0 is not a statement about code quality or feature completeness. It’s a communication commitment — “from here on, breaking changes will announce themselves.” If people are building on your API in production, they deserve that commitment even if the roadmap feels unfinished. Many of the most-depended-on packages sat at 0.x for years, and their users paid for it in surprise breakage.
Pre-release and Build Metadata
Two extensions cover the gray areas:
- Pre-release tags:
3.0.0-beta.2— a version that exists but isn’t stable yet. Sorts before3.0.0, and package managers won’t auto-install it. The identifiers are up to you (alpha,beta,rc.1). - Build metadata:
3.0.0+20260728.sha.5114f85— information after a+that’s ignored entirely for precedence. Useful for traceability, invisible to version comparison.
What Each Bump Means for Your Changelog
Here’s the part the spec leaves implicit: every bump type carries a different communication obligation, because each answers a different reader question.
A PATCH release needs one line per fix in the changelog. Nobody reads patch notes for fun; they read them to confirm a specific bug they hit is gone. Write “Fixed: sessions no longer expire early when ‘remember me’ is checked” and you’re done.
A MINOR release is where features go unnoticed if you let them. The changelog entry should sell the addition — what it does, why it matters, how to try it. This is where the what/why/how entry format earns its keep.
A MAJOR release is a migration document, not an announcement. Every breaking change needs: what changed, who’s affected, what to do about it, and when the old behavior disappears. Our API release notes guide covers the deprecation-window mechanics — the headline rule is that the version number alone is not communication; it’s the promise that communication exists.
The formats connect end to end. Conventional Commits makes the bump computable from your commit history — fix: implies PATCH, feat: implies MINOR, BREAKING CHANGE: implies MAJOR — which is how tools like semantic-release cut versions automatically. Keep a Changelog then gives each version its own dated, categorized section. Version numbers, commits, and changelog entries stop being three separate chores and become one pipeline.
Where SemVer Doesn’t Fit
Honesty section: SemVer is a contract for software that people depend on programmatically — libraries, CLIs, APIs. It fits some things badly:
- Continuously deployed SaaS. Your users don’t install versions; there’s nothing for
^2.14.0to protect. Most SaaS changelogs organize by date instead, and that’s correct. (Your public API is the exception — version that.) - Marketing-driven versioning. When the major number bumps because it’s September (browsers, some enterprise software), the numbers encode time or marketing, not compatibility. That’s calendar versioning wearing SemVer’s clothes — fine, but don’t call it SemVer.
- Internal tools with one consumer. The ceremony costs more than the contract is worth when you control both sides.
The test: if nothing breaks for anyone when you get a version number “wrong,” you don’t need SemVer — you need a dated changelog.
The Bump-Decision Checklist
When you’re unsure what to bump, walk down:
- Will any documented, existing usage stop working, change behavior, or require action? → MAJOR
- Can anyone now do something they couldn’t before? → MINOR
- Otherwise → PATCH
Edge cases teams argue about, resolved by the spec’s logic: fixing a bug people depended on is still a PATCH by the letter but a MAJOR by kindness if the “bug” was documented behavior — when in doubt, bump higher and say why. Deprecating (without removing) is a MINOR with a loud changelog entry. Performance improvements are PATCH unless they change documented limits. Dependency upgrades follow whatever they expose to your users.
Versioning Is Communication
The whole scheme reduces to one idea: a version number is the smallest release note you ship. Three numbers, one compatibility promise, readable by humans and package managers alike. The changelog is where the promise gets explained — and if your releases already flow from commits to drafted release notes automatically, the version bump becomes one more thing the pipeline gets right without a meeting.
Further Reading
Frequently Asked Questions
What are the rules for semantic versioning?
A semantic version has three numbers: MAJOR.MINOR.PATCH. Increment MAJOR when you make incompatible changes that break existing users, MINOR when you add functionality in a backward-compatible way, and PATCH when you make backward-compatible bug fixes. Once a version is published it never changes — you only ever release new ones. The full specification lives at semver.org.
What does a 0 major version mean?
Anything at 0.x.y is declared unstable: the public API is not settled, and anything may change at any time. In practice the ecosystem treats 0.x minor bumps (0.4.0 → 0.5.0) as potentially breaking. Shipping 1.0.0 is the promise that breaking changes will now announce themselves with a major bump — it's a communication commitment, not a code milestone.
What is the difference between semantic versioning and other version numbering?
Semantic versioning encodes meaning: each of the three numbers answers a specific question about compatibility. Other schemes encode different things — calendar versioning (like Ubuntu's 24.04) encodes the release date, build numbers encode sequence, and marketing versions encode significance. SemVer's advantage is that machines can act on it: package managers auto-accept patch and minor updates precisely because the numbers carry a compatibility contract.
Does semantic versioning apply to SaaS products?
Loosely. SemVer was designed for software with a public API that others depend on — libraries, CLIs, actual APIs. A continuously deployed web app has no version its users install, so most SaaS changelogs organize by date instead. The exception is your API: if developers build against it, versioning it semantically (or at least signalling breaking changes explicitly) is one of the kindest things you can do for them.
How does semantic versioning relate to changelogs?
The version number is a compressed changelog entry: MAJOR says 'breaking — read before upgrading,' MINOR says 'new capabilities, safe to take,' PATCH says 'fixes only.' Formats like Keep a Changelog make the connection explicit by organizing the changelog into one section per version, and Conventional Commits closes the loop by making commit types (feat, fix, breaking) compute the next version number automatically.
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