Semantic Versioning Cheat Sheet
The MAJOR.MINOR.PATCH semantic versioning specification for communicating compatibility in software releases.
Version Format
The three-part core version number and what changes to each part signal.
- MAJOR- Incremented for incompatible/breaking API changes, e.g. 1.x.x to 2.0.0
- MINOR- Incremented for backward-compatible new functionality, e.g. 1.2.x to 1.3.0
- PATCH- Incremented for backward-compatible bug fixes, e.g. 1.2.3 to 1.2.4
- Initial development- 0.y.z means anything may change at any time; the public API should not be considered stable
Version Examples
Reading real version strings against the spec.
1.0.0 # First stable public release1.1.0 # Added new backward-compatible feature1.1.1 # Bug fix, no new features2.0.0 # Breaking change, e.g. removed/renamed an API1.0.0-alpha # Pre-release, lower precedence than 1.0.01.0.0+20260708 # Build metadata, ignored when comparing precedence
npm/semver Range Operators
Common range syntax used in package.json dependencies.
{ "dependencies": { "lodash": "^4.17.21", "express": "~4.18.0", "react": "18.2.0", "typescript": ">=5.0.0 <6.0.0" }}
Range Operator Meaning
What each caret/tilde operator actually allows.
- ^ (caret)- Allows changes that do not modify the leftmost non-zero digit, e.g. ^4.17.21 allows 4.x.x but not 5.0.0
- ~ (tilde)- Allows patch-level changes only, e.g. ~4.18.0 allows 4.18.x but not 4.19.0
- Exact pin- No operator means only that exact version is accepted
- Pre-release precedence- 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0
Pre-release Precedence Comparison
How SemVer orders pre-release identifiers when comparing two versions (spec §11).
# Precedence is determined by comparing dot-separated identifiers# left to right:# - numeric identifiers compare numerically# - alphanumeric identifiers compare lexically (ASCII)# - numeric identifiers always have LOWER precedence than# alphanumeric identifiers of the same position# - a larger set of fields has higher precedence than a smaller# set, if all preceding identifiers are equal1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0# Build metadata (+...) is ALWAYS ignored for precedence:1.0.0+build.1 == 1.0.0+build.2 # equal precedence
Driving SemVer from Conventional Commits
Machine-derive the next version bump from commit message prefixes instead of a human deciding.
# Conventional Commits prefixes map to a bump level:# fix: ... -> PATCH# feat: ... -> MINOR# feat!: ... or a -> MAJOR# 'BREAKING CHANGE:' footergit log --oneline v1.4.2..HEAD# fix(auth): handle expired refresh token# feat(api): add cursor-based pagination# feat(api)!: remove deprecated /v1/users endpoint# semantic-release / release-please inspect these and compute:npx semantic-release --dry-run# -> Next release version: 2.0.0 (MAJOR, due to feat!)
Range Overlap & Peer Dependency Conflicts
Why 'it satisfies semver' doesn't guarantee a single install can resolve.
{ "dependencies": { "react": "^18.2.0" }, "peerDependencies": { "react": ">=16.8.0 <19.0.0" }}// npm/yarn must find ONE version of react satisfying every// constraint in the graph simultaneously. Two packages each// pinning incompatible exact versions (react: 17.0.0 vs// react: 18.2.0 with no operator) cannot both be satisfied// with a single copy -> npm ERESOLVE, or duplicate installs// in node_modules (allowed for non-singleton packages, not// for things like React that break with duplicates)
Spec Edge Cases Worth Knowing
Rules from the formal SemVer 2.0.0 spec that are easy to get wrong.
- 0.y.z is exempt from MAJOR rules- Anything can break between 0.1.0 and 0.2.0; only the move from 0.x to 1.0.0 signals API stability
- Public API must be declared- SemVer requires you define what counts as your public API (spec §1) — CLI flags, HTTP response shape, and exported types all count if you claim them
- Version MUST NOT be reused- Once 1.2.3 is released, that exact version+content is immutable forever, even if yanked from a registry
- Patch bump is mandatory for bug fixes- A backward-compatible bug fix without a PATCH bump violates the spec, since consumers pin ranges expecting fixes to arrive that way
- Build metadata is not part of precedence- 1.0.0+exp.sha.5114f85 and 1.0.0+build.789 have identical precedence to plain 1.0.0, so tools should not sort by it
- Major version zero has no stability guarantee- Libraries at 0.x can and do break MINOR-to-MINOR; treat 0.x dependencies as pinned exact versions, not ranged
Versioning Multiple Packages (Monorepos)
How SemVer is applied when one repo ships many independently-consumed packages.
- Independent versioning- Each package gets its own version bumped only when it changes (e.g. Lerna 'independent' mode, Changesets default)
- Fixed/locked versioning- All packages in the repo share one version number and bump together, simplifying compatibility claims at the cost of noisy releases
- Changesets workflow- Contributors add a markdown changeset describing impact (patch/minor/major) per PR; a release tool aggregates them into version bumps and changelogs
- Cross-package peer ranges- Internal packages typically declare workspace:^ or workspace:* so the monorepo tool rewrites them to real semver ranges only at publish time
A breaking change is defined by your public API contract, not your intent — removing a field from a JSON response or changing default behavior counts as MAJOR even if you consider it 'just a fix', because it can break consumers silently.