SemVer Cheatsheet - Semantic Versioning Reference

A careless constraint like ^ or ~ silently decides whether your deploy upgrades dependencies or breaks the build. This reference covers MAJOR.MINOR.PATCH structure, prerelease tags, comparison operators, and the npm ranges you actually write (^ ~ >= < * and advanced combos). Use it to write a package.json that upgrades exactly what you want, and to tell why npm picked version X. After reading you read and write version ranges with intent.

Reference·33 commands·Last updated 2026-07-21
semverVersioningnpmConventions

Typical Use Case

For developers maintaining npm packages, CLI tools and dependency pinning. Semantic versioning splits a version into MAJOR.MINOR.PATCH, where each increment signals a different kind of change; this sheet also maps changes to segments (feat → MINOR, fix → PATCH, BREAKING CHANGE → MAJOR). The lower half is the real point: how to write range expressions safely. ^1.2.3 permits the whole 1.x line while ~1.2.3 stays inside 1.2.x — a one-order-of-magnitude difference at the boundary. Pre-release versions and build metadata follow separate comparison rules. Pin exactly with =1.2.3 or the range 1.2.3 - 2.3.4.

Version Number Structure 5

MAJOR.MINOR.PATCH
The three-part version scheme: major.minor.patch
1.2.3
Example: major 1, minor 2, patch 3
MAJOR: breaking API change
Incremented on breaking API changes
MINOR: new backward-compatible features
Incremented when backwards-compatible features are added
PATCH: backward-compatible bug fixes
Incremented on backwards-compatible bug fixes

Pre-release & Build Metadata 6

1.0.0-alpha.1
Internal alpha build
1.0.0-beta.2
Public beta build
1.0.0-rc.1
Release candidate
alpha < beta < rc < final
Prerelease precedence order: alpha before beta before rc before the final release
1.0.0-alpha < 1.0.0-alpha.1
Same identifier, compared by the incrementing numeric suffix
1.0.0+20260721
Build metadata after the + sign, ignored for precedence

Version Comparison Operators 5

=1.2.3
Exactly the specified version
>1.2.3
Greater than the specified version, exclusive
>=1.2.3
Greater than or equal to the specified version
<2.0.0
Less than the specified version, exclusive
<=1.9.9
Less than or equal to the specified version

npm Version Range Symbols 7

^1.2.3
Allows 1.x.x but not 2.0.0, the compatible-version range
~1.2.3
Allows 1.2.x but not 1.3.0, the approximate-version range
1.2.3 - 2.3.4
Hyphen range, >=1.2.3 and <=2.3.4
1.x
Wildcard, equivalent to >=1.0.0 <2.0.0
1.2.x
Wildcard, equivalent to >=1.2.0 <1.3.0
*
Any version; not recommended, it easily pulls in breaking upgrades
latest
The latest stable release, a dist-tag

npm Advanced Range Syntax 5

1.2.3 || >=2.0.0
OR operator, matches either range
>=1.2.3 <2.0.0
A space means AND, both conditions must hold
~1.2 || ^2.0.0
Combining compound ranges
github:owner/repo
Install from a Git repository
file:./local-pkg
Install from a local path

Version Changes & Release Rules 5

feat: new feature
Bumps MINOR
fix: bug fix
Bumps PATCH
BREAKING CHANGE: breaking change
Bumps MAJOR
0.x.x
Major version 0 means early development, the API is unstable
1.0.0
The first stable release, marking the public API as stable

Parameter matrix

ParameterEffectExample
MAJOR.MINOR.PATCHthe three segments, mapping to incompatible change, compatible feature and compatible fix1.2.3 :: major 1, minor 2, patch 3
^1.2.3compatible range: every 1.x patch and minor, blocked before 2.0.0^1.2.3 :: allows 1.x.x, rejects 2.0.0 (compatible version)
~1.2.3close range: only 1.2.x is allowed, so 1.3.0 is rejected~1.2.3 :: allows 1.2.x, rejects 1.3.0 (close version)
pre-release tag alpha/beta/rca pre-release ranks below the final release; the same tag compares by its number1.0.0-alpha < 1.0.0-alpha.1
+ build metadatarecords build info while playing no part at all in precedence comparison1.0.0+202****0721
range and or operatorsa space means AND, a double pipe means OR, covering needs that span major versions~1.2 || ^2.0.0
commit message prefixlets conventional commits drive the next version number automaticallyBREAKING CHANGE: a breaking change :: bumps MAJOR

Common pitfalls

SymptomA pre-release like 1.0.0-alpha was installed even though 1.0.0 was expected to win.

CauseA version with a pre-release tag ranks below the final release, and pre-releases are not selected automatically.

FixWrite 1.0.0-alpha explicitly to get it; ship 1.0.0 rather than continuing on alpha.

SymptomTwo versions differing only in build number are treated as distinct in precedence, or as identical when they are not.

CauseBuild metadata after the + sign is an identifier only and takes no part in precedence comparison.

FixUse the + suffix to distinguish artifacts, and compare precedence using only the three segments plus any pre-release tag.

SymptomCI breaks one day out of nowhere because upstream published a breaking release.

Cause* accepts anything and ^ allows the whole minor line, so a MAJOR bump upstream introduces incompatible changes.

FixPin in CI with a lock or exact range such as 1.2.3 - 2.3.4, and reserve ^ for application code.

SymptomBehavior of a 0.x dependency changed after a patch-level install.

CauseA major version of 0 signals an unstable API, so even a MINOR bump can break compatibility.

FixConstrain 0.x dependencies with ~0.x.y so the range stays inside one minor line.

Troubleshooting

  1. 1See which versions a range expression actually covers

    npm view lodash@'^1.2.3' versions --json

    The printed list is what the range resolves to; versions outside it mean the range is too narrow or malformed.

  2. 2Confirm the installed version matches the declared dependency tree

    npm ls --depth=0

    An invalid marker or UNMET DEPENDENCY means the locked version conflicts with the declared range.

  3. 3Derive the next version number from commit messages

    npx standard-version --dry-run

    dry-run only prints which segment feat, fix and BREAKING CHANGE would bump, writing nothing.

Command Examples

Ordering prereleases against releases by SemVer rules

node -e " function cmp(a, b) { const A = a.split(/[.+-]/), B = b.split(/[.+-]/); const pa = a.includes('-'), pb = b.includes('-'); for (let i = 0; i < 3; i++) { const d = +A[i] - +B[i]; if (d) return d; } if (pa !== pb) return pa ? -1 : 1; if (pa && pb) { const x = a.split('-').slice(3), y = b.split('-').slice(3); for (let i = 0; i < Math.max(x.length, y.length); i++) { if (x[i] === undefined) return -1; if (y[i] === undefined) return 1; if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1; } } return 0; } const v = ['1.0.0-alpha', '1.0.0-alpha.1', '1.0.0-beta.2', '1.0.0-rc.1', '1.0.0', '1.0.1', '1.2.0', '2.0.0']; console.log(v.sort(cmp).join(' < ')); "

Compare the first three numbers segment by segment, then apply the rule that a version carrying a prerelease identifier sorts below the plain release, and finally compare identifiers numerically.

Output

1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-rc.1 < 1.0.0 < 1.0.1 < 1.2.0 < 2.0.0

Expanding caret and tilde into real bounds

node -e " function expand(r) { const m = r.match(/^([\^~]?)(\d+)\.(\d+)\.(\d+)$/); const [, op, M, mi] = m; const MA = +M; if (op === '^') return MA > 0 ? '>=' + r.slice(1) + ' <' + (MA + 1) + '.0.0' : '>=' + r.slice(1) + ' <' + MA + '.' + (+mi + 1) + '.0'; if (op === '~') return '>=' + r.slice(1) + ' <' + MA + '.' + (+mi + 1) + '.0'; return '=' + r; } for (const r of ['^1.2.3', '~1.2.3', '^0.2.3', '^0.0.3']) console.log(r.padEnd(8), '==>', expand(r)); "

With a non-zero major, the caret only blocks the next major; with major 0 it blocks the next minor instead, which is exactly why upgrades are risky during the 0.x phase.

Output

^1.2.3   ==> >=1.2.3 <2.0.0
~1.2.3   ==> >=1.2.3 <1.3.0
^0.2.3   ==> >=0.2.3 <0.3.0
^0.0.3   ==> >=0.0.3 <0.1.0

Confirming build metadata stays out of precedence

node -e " function cmp(a, b) { const pa = a.includes('-'), pb = b.includes('-'); const A = a.split(/[.+-]/), B = b.split(/[.+-]/); for (let i = 0; i < 3; i++) { const d = +A[i] - +B[i]; if (d) return d; } if (pa !== pb) return pa ? -1 : 1; return 0; } console.log(cmp('1.0.0+build.1', '1.0.0+build.2'), cmp('1.0.0', '1.0.0')); "

Both results are zero: versions with differing build identifiers and the plain version all share identical precedence.

Output

0 0

Deriving the next version from the commit prefix

node -e " const R = [['fix: x', '1.2.3'], ['feat: x', '1.2.3'], ['BREAKING CHANGE: x', '1.2.3']]; for (const [msg, v] of R) { const [M, m, p] = v.split('.').map(Number); const next = msg.startsWith('BREAKING') ? (M + 1) + '.0.0' : msg.startsWith('feat') ? M + '.' + (m + 1) + '.0' : M + '.' + m + '.' + (p + 1); console.log(msg, '->', next); } "

Three commit messages all starting from 1.2.3 land on PATCH, MINOR and MAJOR respectively, matching the conventional-commits convention.

Output

fix: x -> 1.2.4
feat: x -> 1.3.0
BREAKING CHANGE: x -> 2.0.0

Common Pitfalls

  • Prereleases are not installed by default: ^1.0.0 resolves to a stable release, and getting alpha or rc requires spelling the identifier into the range.
  • Build metadata is excluded from precedence, yet npm still treats differing build identifiers as distinct artifacts, so mixing them triggers duplicate-dependency conflicts.
  • Writing a dependency as * or omitting the version hands resolution to upstream; the day they ship a breaking release, CI turns red on its own.
  • During the 0.x phase the caret blocks the next minor, so moving from 0.2.x to 0.3.0 already carries a breaking change — major-version intuition does not apply.
  • A breaking change must increment the major version even when it is a one-line edit; disguising it as MINOR breaks downstream on the next automatic upgrade.

Tips

  • ^1.2.3 equals >=1.2.3 <2.0.0 — npm install default behavior.
  • ~1.2.3 equals >=1.2.3 <1.3.0 — only patch updates allowed.
  • package-lock.json locks exact versions, avoiding uncertainty from ^ and ~.
  • Breaking changes must bump the major version, even for tiny changes.
  • At major version 0 (0.x.x), any breaking change should bump the minor version.

FAQ

Why do prereleases such as 1.0.0-alpha not satisfy ordinary ranges?

The specification isolates them. A range like ^1.0.0 matches stable releases only, and although 1.0.0-alpha sorts below 1.0.0 numerically it does not satisfy the range unless you spell the prerelease identifier out, as in ^1.0.0-alpha. The rule exists so npm install cannot land on a test build by accident: ^1.0.0 means the stable channel, and only an explicit expression buys you alpha, beta or rc.

If build metadata does not affect precedence, what is it for?

It is a label for humans. Everything after the + sign is excluded from comparison: 1.0.0+build.1 and 1.0.0+build.2 have identical precedence and count as the same version. Its purpose is recording build time, commit hash or packaging environment. The one trap is npm, which still treats two differing build identifiers as distinct artifacts and can end up complaining about duplicate dependencies.

How should version numbers work while the major version is 0?

A major of 0 signals that the public API is not stable yet, so MINOR carries the breaking changes. Expanding ^0.2.3 yields >=0.2.3 <0.3.0: bumping the minor version locks out existing consumers, and that is deliberate protection. During the 0.x phase treat MINOR as the next incompatible generation and reserve PATCH for safe fixes, then publish 1.0.0 once the API is stable and hand the compatibility promise to consumers.

Why does a * dependency suddenly break CI?

Ranges are resolved continuously, not locked once. A * means each install picks whatever is newest at that moment, so the day upstream ships 2.0.0 your source is unchanged while the resolution is not. A lockfile pins exact versions and turns resolution into a one-time decision: libraries should ship loose ranges so downstream users keep freedom, while applications must commit a lockfile so the result is fixed.

What ranges do the wildcard forms 1.x and 1.2.x stand for?

1.x equals >=1.0.0 <2.0.0, opening the entire major version; 1.2.x equals >=1.2.0 <1.3.0, opening only the patch level inside that minor. A wildcard simply marks one segment as any value, so in 1.x that segment is ignored entirely, including any patch number already present there. Wildcards are convenient but hurt reproducibility, so projects relying on them should still commit a lockfile.

Official References

Each command links to its official documentation below, so you can verify the latest usage and read deeper.

Maintained by LaoHand

Publicly updated on Jul 21, 2026, continuously proofread against official docs.

Contact Us

Wrong command or description? Send us corrections, business inquiries or product feedback by email.

Contact Us