Semantic versioning is a published specification for turning a version number into three parts, MAJOR.MINOR.PATCH, each signalling a kind of change. The number communicates compatibility, not effort or marketing. What most guides skip: each bump implies what the changelog entry beside it needs to contain.
This page is one part of a larger guide. For the whole subject in one place, see changelogs and release notes.
What does MAJOR.MINOR.PATCH actually mean?
Three numbers, separated by dots, each incremented for a different reason: MAJOR for an incompatible change to the public interface, MINOR for functionality added in a backwards-compatible way, PATCH for a backwards-compatible fix. Whichever number changes, everything to its right resets to zero.
The common misreading is to treat the numbers as a measure of effort. A MAJOR bump is not "we did a lot of work," it is "something that depended on the old behaviour will now need to change." A PATCH is not "this was small," it is "nothing that depended on this will need to change at all." Very different amounts of effort can produce the same category of bump, because the number describes the promise, not the work behind it.
What does the specification promise, exactly?
That the version number is a contract about compatibility, checkable without reading a line of the changelog. Once a project declares 1.0.0, the public interface is defined and stable, and every later number states how that interface has or has not moved since.
Versions before 1.0.0 sit outside that promise. The specification reserves 0.y.z for initial development, and says plainly that anything may change at any time during that phase. A project on 0.4.0 is not breaking a promise by shipping an incompatible change in 0.5.0, because the only promise 0.y.z makes is that there is no promise yet.
Major version zero (0.y.z) is for initial development. Anything MAY change at any time.
Semantic Versioning 2.0.0, clause 4
What are pre-release and build metadata for?
Two optional suffixes doing different jobs. A pre-release identifier is appended after a hyphen, as in 2.5.0-beta.1, marking a version as coming before the normal version it is attached to: not yet stable, not guaranteed to hold the same compatibility promise. Build metadata is appended after a plus sign, as in 2.5.0+build.317, identifying a specific build without affecting precedence: two versions differing only in build metadata are equal for ordering.
2.5.0-beta.1+build.317
Read left to right: 2.5.0 is the version being previewed, beta.1 marks it as a pre-release of that version, and build.317 identifies which build produced it, without changing where it sits against any other version.
Convention, not the specification, is what most teams do with these: pre-release tags for release candidates and betas, build metadata for internal traceability such as a commit hash nobody outside the team needs to reason about.
What changelog entry does a MAJOR release need?
A migration section, and an explicit list of what breaks. This is the bump where the changelog's job changes from informing to instructing: a reader on the previous major version needs to know, specifically, what in their setup will stop working and what to do before they update.
Name each breaking change on its own line, with the shape of the fix: a renamed setting, a removed option, a changed default. "Several breaking changes, see the documentation" is not a changelog entry, it is a redirect, and it will not stop the support requests that follow.
What changelog entry does a MINOR release need?
A description of the new capability, in terms of what the reader can now do rather than how it was built. This is the bump closest to an ordinary release note: something was added, nothing that worked before stopped working, and the entry's job is making sure the reader notices.
"Added CSV export for the roadmap board" tells a reader everything needed to decide whether to try it. A MINOR entry needs no migration section, because by definition nothing has to change for existing behaviour to keep working. For more entries in that shape, see changelog examples worth copying.
What changelog entry does a PATCH release need?
Just enough to decide whether it is worth the update. A patch changes nothing about how the product behaves for anyone not already hitting the problem being fixed, so the entry's only job is letting an affected reader recognise themselves in it. The Keep a Changelog format pairs this with a named category: a Fixed-only release is exactly the kind that bumps PATCH and nothing else.
| Bump | Signals | What the entry needs |
|---|---|---|
| MAJOR, 1.x to 2.0.0 | Something in the public interface will break | A migration section and an explicit, itemised list of what breaks and how to adapt |
| MINOR, 1.2.x to 1.3.0 | A new, backwards-compatible capability | What the reader can now do, described plainly, no migration needed |
| PATCH, 1.2.3 to 1.2.4 | A backwards-compatible fix, nothing added | Enough detail that a reader can tell whether it affects them, in one line |
One payment, no subscription, unlimited products.
What if a team ships continuously and the scheme does not fit?
It genuinely does not fit every team. Semantic versioning assumes discrete releases with a defined public interface that other software or people depend on directly: a library, an API, a package another project imports. A team pushing to production several times a day, with no external consumer parsing the version number, is applying a scheme designed for a problem it does not have.
Calendar versioning is a legitimate alternative here. Instead of MAJOR.MINOR.PATCH signalling compatibility, a date-based version such as 2026.08 or 2026.08.22 signals when a build was cut. That suits products with no meaningful concept of a breaking change from the user's point of view, and teams who release on a schedule rather than around features. What it gives up is exactly what semantic versioning provides: nobody can look at 2026.09 against 2026.08 and know whether anything will break. If nothing forces that question, the trade costs nothing; if it does, calendar versioning is quietly hiding the one thing a version number was supposed to answer.
Is a version number worth keeping if nobody acts on it?
Only if someone can do something with the information. A version number nobody checks before updating, sitting atop a changelog nobody reads, is decoration: it looks like communication without doing the job. If a reader cannot use it to decide whether to update now, wait, or check for a breaking change first, the scheme is running for its own sake.
That cuts both ways. A team with real external consumers, an API other software calls, a library other projects import, gets genuine value from strict MAJOR.MINOR.PATCH discipline, because someone downstream makes real decisions from that number. A team shipping a single, always-current install with no external interface may get more value from calendar versioning, or just dates with no version number at all.
Frequently asked questions
Does semantic versioning apply to changelog entries, or only to the version number?
Only to the number: the specification defines what MAJOR, MINOR and PATCH mean, not how to write prose. In practice the two should agree, because an entry describing a breaking change beside a PATCH-only number tells the reader two contradictory things at once.
What counts as a breaking change for the purposes of a MAJOR bump?
Anything that stops existing, correct usage of the public interface from working as it did before: a removed feature, a renamed setting with no fallback, a changed default nobody opted into. If existing usage keeps working unchanged, it is not a MAJOR-level break.
Can a version go from 1.9.0 straight to 2.0.0 with no MINOR releases in between?
Yes. Nothing requires passing through every possible MINOR or PATCH number; a project can jump the moment a breaking change ships, whether or not 1.10.0 ever existed.
Is 0.1.0 to 0.2.0 a breaking change under semantic versioning?
The specification does not classify it either way, because the whole 0.y.z range sits outside its stability promise. A jump within 0.y.z is not a violation, even if it breaks something.
Should a small team bother with pre-release tags like beta or rc?
Only if a real audience tries the version before it is final. A pre-release tag marks a build as not yet meeting the stability promise it precedes; if nobody outside the team installs an in-between build, a plain version bump when the work is done is simpler.