Semantic Versioning (SemVer) Explained: MAJOR.MINOR.PATCH
Semantic Versioning, almost always abbreviated to SemVer, is a convention for assigning version numbers that communicate the nature of a change, not just its order. Instead of an opaque counter, a SemVer version tells a consumer at a glance whether an upgrade is safe, additive, or potentially breaking.
What semantic versioning actually is
SemVer is a published specification (currently version 2.0.0, authored by Tom Preston-Werner) that defines a version as three non-negative integers in the form MAJOR.MINOR.PATCH, for example 2.4.1. Each number has a specific meaning, and you increment exactly one of them per release based on what changed in the public API.
The contract is simple: given a version X.Y.Z, you increment MAJOR when you make an incompatible (breaking) API change, MINOR when you add functionality in a backward-compatible way, and PATCH when you make a backward-compatible bug fix. When you bump a number to the left, every number to its right resets to zero, so the patch after 1.4.9 that adds a feature becomes 1.5.0, and the next breaking change becomes 2.0.0.
Reading MAJOR.MINOR.PATCH
The three core fields answer three different questions for whoever depends on your software.
| Field | Increment when | Example | Consumer impact |
|---|---|---|---|
| MAJOR | You break backward compatibility | 1.9.2 → 2.0.0 | May require code changes to upgrade |
| MINOR | You add backward-compatible features | 1.9.2 → 1.10.0 | Safe to upgrade; new capabilities available |
| PATCH | You fix bugs without changing the API | 1.9.2 → 1.9.3 | Safe to upgrade; behavior corrected |
A few specifics catch people out. Version numbers are compared field by field as integers, so 1.10.0 is higher than 1.9.0 even though 10 sorts before 9 as text. Leading zeros are not allowed in any field. And the spec treats 0.y.z as a special initial-development phase where anything may change at any time, so the stability guarantees above only fully apply once you release 1.0.0.
Pre-release and build metadata
SemVer also defines two optional suffixes. A pre-release identifier is appended with a hyphen, as in 1.0.0-alpha, 1.0.0-beta.2, or 1.0.0-rc.1. A pre-release version has lower precedence than the associated normal release, so 1.0.0-rc.1 comes before 1.0.0. This is exactly what you want: release candidates should not satisfy a request for the finished version.
Build metadata is appended with a plus sign, as in 1.0.0+20130313144700 or 1.0.0+exp.sha.5114f85. Build metadata is explicitly ignored when determining precedence, so 1.0.0+build.1 and 1.0.0+build.99 are considered the same version for ordering purposes. Use it for traceability (commit hashes, timestamps), never to distinguish releases that should actually differ.
Why it matters: version ranges and dependency resolution
SemVer's real payoff appears in package managers. Because the meaning of each digit is fixed, tools can express ranges that automatically accept safe updates while excluding breaking ones. In the npm ecosystem the two common operators are the caret and the tilde, and they map directly onto the SemVer fields.
^1.2.3 allows >=1.2.3 and <2.0.0 (minor + patch updates)
~1.2.3 allows >=1.2.3 and <1.3.0 (patch updates only)
1.2.3 exact pin, no automatic updates
The caret is the npm default and trusts that minor and patch releases are backward compatible, which is precisely the promise SemVer makes. The tilde is more conservative, accepting only patch-level fixes. There is one wrinkle for early-stage packages: under 0.x versions, npm narrows the caret so that ^0.2.3 allows only >=0.2.3 <0.3.0, because a 0.x minor bump is allowed to break things. You can compare specific versions and ranges with our SemVer Comparator, and inspect or edit a package.json with the JSON Formatter.
How to apply SemVer in practice
Adopting SemVer well is mostly about discipline around your public API. The spec is clear that you must declare what your public API is; only changes to that surface drive version bumps. Internal refactors that leave observable behavior unchanged are patch releases at most.
- Decide what counts as breaking. Removing or renaming an exported function, changing a function's required parameters, altering a return shape, or changing documented behavior are all MAJOR-level changes.
- Treat additions as MINOR. A new optional parameter with a default, a new exported helper, or a new configuration flag are backward-compatible additions.
- Keep a changelog. Conventional Commits and automated tools can derive the correct bump from commit messages. A human-readable changelog pairs naturally with SemVer; generate one with the Changelog Generator.
- Never re-release a version. Once a version is published, its contents are immutable. If you shipped a mistake, publish a new version rather than overwriting.
Common pitfalls and misconceptions
The most frequent error is bumping the wrong field, especially shipping a breaking change as a minor or patch release. This silently breaks every consumer using a caret range, because they accepted the update believing the SemVer promise held. When in doubt about whether a change is breaking, treat it as breaking.
Another misconception is that 2.0.0 must contain large or impressive features. A MAJOR bump signals incompatibility, not scope; removing a single deprecated function justifies a MAJOR release even if nothing else changed. Conversely, a release packed with new features is still MINOR if it breaks nothing.
People also forget that 0.x versions carry no compatibility guarantee, so depending on a 0.x package without pinning is risky. And SemVer is independent of release cadence: it says nothing about how often you ship or on what dates, which is what distinguishes it from calendar-based schemes like CalVer. If you regularly work with version strings, commit conventions, and configuration formats, our broader Git Cheat Sheet and the explainer on Git workflow best practices cover the surrounding workflow.
Frequently Asked Questions
Releasing 1.0.0 declares that your public API is stable and that you are now committing to the SemVer compatibility rules. Before 1.0.0, the 0.x phase explicitly allows any change at any time, so 1.0.0 is the point where consumers can safely rely on caret ranges.
Increment MAJOR for any backward-incompatible (breaking) change to your public API, MINOR for backward-compatible new functionality, and PATCH for backward-compatible bug fixes. Bumping a higher field resets all lower fields to zero.
In npm, ^1.2.3 accepts minor and patch updates below 2.0.0, while ~1.2.3 accepts only patch updates below 1.3.0. Both rely on SemVer's promise that minor and patch releases are backward compatible. You can test ranges with the SemVer Comparator at /tools/semver-comparator.
No. Build metadata after a plus sign (for example 1.0.0+build.5) is ignored when comparing precedence, so two versions that differ only in build metadata are treated as equal. Pre-release identifiers after a hyphen, by contrast, do lower precedence below the matching normal release.
No. A MAJOR version bump signals incompatibility, not scope. Removing a single deprecated function is a breaking change and warrants a MAJOR release, while a large batch of purely additive features is still a MINOR release.