Semantic versioning, in plain English

Semantic versioning ("semver") is three numbers and one promise. The numbers are easy; the promise is where everyone argues. This guide explains what the spec actually commits you to, in plain English, plus the judgment calls the spec can't make for you.

The promise

A semver version is MAJOR.MINOR.PATCH, e.g. 2.4.1. Reading right to left:

  • PATCH (2.4.1 β†’ 2.4.2): "I fixed something. Nothing you depend on changed. Upgrade blind."
  • MINOR (2.4.1 β†’ 2.5.0): "I added something. Everything that worked still works. Upgrade blind; read the changelog if you want the new toys."
  • MAJOR (2.4.1 β†’ 3.0.0): "I broke something on purpose. Do not upgrade blind β€” read the migration notes first."

That's the entire contract. The version number is not marketing, not a birthday counter, and not a measure of effort. It answers exactly one question a downstream user has: "can I upgrade without reading anything?"

What actually counts as "breaking"

Breaking means: code or workflows that were correct before are wrong now. The subtle cases trip everyone up:

  • Removing or renaming anything public β€” obviously breaking. Endpoints, exported functions, CLI flags, config keys, response fields. Removal should follow a published deprecation policy, not a mood.
  • Changing a default β€” breaking. Users who never set the option relied on the old default, whether they knew it or not.
  • Tightening validation β€” breaking. Input that used to be accepted now isn't; someone's pipeline sends exactly that input.
  • Loosening validation, adding response fields, adding optional params β€” usually not breaking, and the reason well-designed APIs tell clients to ignore unknown fields.
  • Fixing a bug people depended on β€” the classic judgment call. The spec says a fix is a patch; reality says if enough users rely on the buggy behavior, it's breaking for them (see "Hyrum's Law"). If the bug is old and load-bearing, treat the fix as major, or at least announce it like one.
  • Raising minimum requirements (Node 18 β†’ 20, dropping an OS) β€” breaking for the people on the old platform. Major.

The 0.x trap

Under the spec, 0.x.y means "anything may change at any time" β€” 0.x has no compatibility promise at all. In practice, half the ecosystem treats 0.MINOR bumps as breaking and 0.x.PATCH as safe, and tooling (npm's caret ranges, for one) encodes exactly that. Two rules of thumb:

  • If you're on 0.x and people are using your thing in production, behave as if the minor number were major: announce 0.7 β†’ 0.8 breakage the same way you'd announce 2.0.
  • Don't camp on 0.x for years out of modesty. If strangers depend on it, it's 1.0 whether you've blessed it or not β€” tagging 1.0.0 just makes your promises legible.

Pre-releases and build metadata

A hyphen suffix marks a pre-release: 2.0.0-beta.1, 2.0.0-rc.2. Pre-releases rank below the release they precede and carry no stability promise β€” they exist so early adopters can opt in. Ship your big migration as an -rc first; your bravest users will find the sharp edges before everyone else hits them. (A +build.20260726 suffix is ignored for ordering entirely β€” it's a note to yourself, not a signal to users.) Writing updates for those pre-release readers is its own craft β€” see beta and early-access release notes.

When semver is the wrong tool

Semver is a contract for things people build on: libraries, APIs, CLIs, plugins, self-hosted software people choose when to upgrade (release notes for CLIs and libraries covers writing for exactly those readers). It is mostly noise for things people log into. A SaaS app deploys continuously; users can't pin the old version, so "2.5.0" tells them nothing actionable. For products like that:

  • Run a dated changelog instead β€” a stream of "what changed, when" entries tagged new / improved / fix. The date does the job the version number was doing. (Some products date the version itself β€” see CalVer vs SemVer.)
  • Keep semver where a contract still exists: your public API can be versioned (/v1/, /v2/) even while the app above it ships daily.
  • When you do break the API, the version bump is not the announcement β€” the announcement is the announcement. See how to announce breaking changes.

Semver and your changelog, together

The two conventions are designed to pair: one Keep a Changelog section per version, breaking changes listed first, and the version number telling readers how carefully to read. If your release flow tags versions in git, your changelog can write itself β€” the tag, the release notes, and the public changelog entry should all come from the same step, or they'll drift.

Quick reference

Did anything that worked before stop working?   β†’ MAJOR
Did you only add or fix things?
  … added something new                          β†’ MINOR
  … fixed/optimized, no interface change         β†’ PATCH
On 0.x with real users?                          β†’ treat MINOR as MAJOR
Product is a SaaS, not a dependency?             β†’ dated changelog, semver the API only

Wakelog is a free hosted changelog built for exactly this workflow: post an entry from the same script that tags the release (git tag v2.5.0 && wakelog post --from-git), or mirror GitHub/GitLab releases automatically. Dated stream, tags, RSS, and a widget β€” no version-number theater required.

Start a free changelog   Next: announcing breaking changes β†’

Related guides

Last updated 2026-07-28 Β· All guides