Introduction
For this system to work, you MUST declare a public API. This MAY consist of documentation or be enforced by the software itself. Regardless, it is important that an API is clear and precise. Changes to your public API are communicated with specific increments to your software's Semantic Version.
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL in this document are to be interpreted as described in RFC 2119.
Versions
Given a semantic version MAJOR.MINOR.PATCH, increment the:
MAJORversion when you make incompatible API changes;MINORversion when you add functionality in a backward compatible manner;PATCHversion when you make a fix for unintended behaviour.
Optional labels for a pre-release and build extend the format:
MAJOR.MINOR.PATCH-PRERELEASE+BUILD.
- Software using semantic versions MUST declare a public API. This API could be declared in the code itself or exist strictly in documentation. However it is done, it SHOULD be precise and comprehensive.
- A normal version number MUST take the form
X.Y.ZwhereX,YandZare non-negative integers, and MUST NOT contain leading zeroes.Xis the major version,Yis the minor version, andZis the patch version. Each element MUST increase numerically. For instance:1.9.0→1.10.0→1.11.0. - Once a versioned package has been released, the contents of that version MUST NOT be modified. Any modifications MUST be released as a new version.
- Major version zero (
0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable. - Version
1.0.0SHOULD be the first stable release and MUST define the public API. - Patch version
Z(x.y.Z | x > 0) MUST be incremented if only a fix for unintended behaviour is introduced. A fix is defined as an internal change that corrects incorrect behaviour. - Minor version
Y(x.Y.z | x > 0) MUST be incremented if new, backward compatible functionality is introduced to the public API. It MUST be incremented if any public API functionality is marked as deprecated. It MAY include patch level changes. Patch version MUST be reset to 0 when minor version is incremented. - Major version
X(X.y.z | X > 0) MUST be incremented if any backward incompatible changes are introduced to the public API. Patch and minor versions MUST be reset to 0 when major version is incremented. - A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version. Identifiers MUST comprise only ASCII alphanumerics and hyphens
[0-9A-Za-z-], MUST NOT be empty, and numeric identifiers MUST NOT include leading zeroes. Pre-release versions have lower precedence than the associated normal version. Examples:1.0.0-alpha,1.0.0-alpha.1,1.0.0-0.3.7,1.0.0-x.7.z.92. - A build MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version. Identifiers MUST comprise only ASCII alphanumerics and hyphens
[0-9A-Za-z-]and MUST NOT be empty. Unlike "build metadata" in later drafts, a build participates in precedence; it is the antithesis of a pre-release. Examples:1.0.0+001,1.0.0+20130313144700,1.0.0-beta+exp.sha.5114f85.
Precedence
Precedence refers to how versions are compared to each other when ordered.
- Precedence MUST be calculated by separating the version into major, minor, patch, pre-release and build identifiers, in that order.
- Major, minor and patch are always compared numerically.
1.0.0 < 2.0.0 < 2.1.0 < 2.1.1 - When major, minor and patch are equal, a pre-release version has lower precedence than a normal version.
1.0.0-alpha < 1.0.0 -
When major, minor, patch and pre-release are equal, a version with
build metadata has higher precedence than one without it. Where SemVer 2.0.0
discards build metadata for ordering, Semantic Versions restores it: a build names a
more specific artifact of that release, so it ranks above the plain release
rather than below it like a pre-release.
1.0.0 < 1.0.0+build.1 < 1.0.0+build.2 -
Precedence for two pre-release or build versions with the same major, minor and patch
MUST be determined by comparing each dot separated identifier from
left to right until a difference is found:
- Identifiers consisting of only digits are compared numerically.
- Identifiers with letters or hyphens are compared lexically in ASCII sort order.
- Numeric identifiers always have lower precedence than non-numeric identifiers.
- A larger set of pre-release (or build) fields has higher precedence than a smaller set, if all preceding identifiers are equal.
1.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 < 1.0.0+build.1
Sets
A Set is a collection of versions. Sets are how the ecosystem actually reasons about software: “every version of this package”, “the 1.x line”, “what this range selects”. Semantic Versions defines them directly, so tools can talk about groups of versions, not just one at a time.
- A Set MUST contain a collection of semantic versions.
- All potential
MAJOR,MINOR,PATCHandPRE-RELEASEversions MUST be interpreted as valid subsets of any set. - A Set MAY be scoped to a context; such a Contextual Set MUST contain only versions sharing that context.
- Fixing any single identifier (
MAJOR,MINOR,PATCH,PRE-RELEASEorBUILD) partitions a set into Subsets. - A Set MAY be sorted according to version precedence.
- Sets MAY contain duplicate versions.
- Duplicate versions in a set MUST have unique build metadata.
- An Unordered Set imposes no ordering; an Ordered Set MUST be sorted by precedence.
Context
A version rarely travels alone; it is understood in context. A
context is the identity a version belongs to: a package,
module, image, crate, service or any other named artifact. It scopes a
version with an @ separator
(context@MAJOR.MINOR.PATCH-PRE+BUILD), so that where a bare
1.2.3 is ambiguous, app@1.2.3-canary.1+sha.1a8c…
names a specific build of a specific thing.
Context is deliberately ecosystem-agnostic. Everything above
(the version grammar, precedence, sets and ranges) makes no assumption about
where a version lives. An ecosystem binds to the spec by qualifying a context
with a source, written source:name, which names
the origin or ecosystem. The same rules then apply identically everywhere:
npm:react@18.2.0 · pypi:flask@3.0.0 ·
gem:rails@7.1.3 · cargo:serde@1.0.203 ·
golang:golang.org/x/text@0.14.0
A source-qualified context is what the extensions
build on, and it lines up with a Package-URL. Where an
ecosystem already has a Package-URL type,
use it as the source, so a reference maps onto a purl one-to-one. The same
spec then covers JavaScript, Python, Ruby, Rust, Go, Java, .NET, PHP and container images
without change:
| Ecosystem | Semantic Versions reference | Package-URL |
|---|---|---|
| JavaScript · npm | npm:react@18.2.0 |
pkg:npm/react@18.2.0 |
| Python · PyPI | pypi:flask@3.0.0 |
pkg:pypi/flask@3.0.0 |
| Ruby · RubyGems | gem:rails@7.1.3 |
pkg:gem/rails@7.1.3 |
| Rust · Cargo | cargo:serde@1.0.203 |
pkg:cargo/serde@1.0.203 |
| Go · modules | golang:golang.org/x/text@0.14.0 |
pkg:golang/golang.org/x/text@0.14.0 |
| Java · Maven | maven:com.google.guava/guava@33.0.0 |
pkg:maven/com.google.guava/guava@33.0.0 |
| .NET · NuGet | nuget:Newtonsoft.Json@13.0.3 |
pkg:nuget/Newtonsoft.Json@13.0.3 |
| PHP · Composer | composer:symfony/console@7.0.4 |
pkg:composer/symfony/console@7.0.4 |
| Containers · OCI | oci:alpine@3.20.0 |
pkg:oci/alpine@3.20.0 |
Namespaces carry across too: an npm scope or a Maven group is part of the name
(npm:@vue/cli@5.0.8, maven:com.google.guava/guava@33.0.0), the
same segment a purl puts in its namespace. A source is just a lowercase
token, so an ecosystem without a Package-URL type (or a private origin) still fits:
napi:sharp@0.33.0, internal:billing@2.4.0.
SubSets
Fixing any one coordinate of a version yields a subset of
context@*. Every position (major, minor, patch, pre-release and
build) defines its own family. The full set <context>@*
is the union of all of them.
<context>@*: every version of a context, partitioned by coordinate
Major SubSet
- 0.*.*
- 1.*.*
- 2.*.*
- 3.*.*
- …
Minor SubSet
- *.0.*
- *.1.*
- *.2.*
- *.3.*
- …
Patch SubSet
- *.*.0
- *.*.1
- *.*.2
- *.*.3
- …
Prerelease SubSet
- *.*.*-0
- *.*.*-1
- *.*.*-2
- *.*.*-3
- …
Build SubSet
- *.*.*+0
- *.*.*+1
- *.*.*+2
- *.*.*+3
- …
Ordered & unordered sets
The same members, two arrangements. An unordered set lists versions as they were encountered; an ordered set sorts them by precedence. Ordering is a property of the set, not of its members.
Contextual sets
A set scoped to a single context (react@*, express@*)
is a contextual set: every version that shares that identity.
Two contextual sets can hold identical version numbers and still be
distinct, because their context differs.
Examples:
- Valid set:
5.6.7, 3.2.1+build.123, 3.2.1, 4.0.0, 1.16.3, 0.9.8 - Invalid set:
3.2.1, 3.2.1 - Ordered set:
0.9.8, 1.16.3, 3.2.1, 3.2.1+build.123, 4.0.0, 5.6.7 - Prerelease set:
1.0.0-pre.1, 1.0.0-pre.2, 1.0.0-pre.3 - Build set:
1.0.0+build.1, 1.0.0+build.2, 1.0.0+build.3
Ranges
A range is a set of comparators which specify the versions
that satisfy the range. A range is composed of one or more comparator sets, joined by
||. A version matches a range if and only if every comparator in at least
one of the ||-separated comparator sets is satisfied by the version.
>=1.2.7 <1.3.0 matches 1.2.7, 1.2.8 and
1.2.99, but not 1.2.6, 1.3.0 or
1.1.0.
1.2.7 || >=1.2.9 <2.0.0 matches 1.2.7,
1.2.9 and 1.4.6, but not 1.2.8 or
2.0.0.
Operators
An operator expresses how to compare a version. Supported operators are:
<— Less than>— Greater than=— Equal to<=— Less than or equal to>=— Greater than or equal to
Comparators
A comparator is composed of an operator and a
version. Comparators can be joined by whitespace to form a
comparator set, satisfied by the intersection of all the
comparators it includes.
>=1.2.7matches1.2.7,1.2.8,2.5.3and1.3.9, but not1.2.6or1.1.0.>1is equivalent to>=2.0.0; it matches2.0.0and3.1.0, but not1.0.1or1.1.0.
Advanced range syntax
Advanced ranges desugar to primitive comparators in deterministic ways.
Hyphen ranges X.Y.Z - A.B.C
Specifies an inclusive set. If a partial version is given as the first version, missing pieces become zeroes; if given as the second, all versions starting with the supplied parts are accepted.
1.2.3 - 2.3.4:=>=1.2.3 <=2.3.41.2 - 2.3.4:=>=1.2.0 <=2.3.41.2.3 - 2.3:=>=1.2.3 <2.4.0-01.2.3 - 2:=>=1.2.3 <3.0.0-0
X-ranges 1.2.x 1.X 1.2.* *
Any of X, x or * may stand in for a numeric value in the [major, minor, patch] tuple. A partial version range is treated as an x-range, so the special character is optional.
*:=>=0.0.0(any stable version). Pre-releases opt in through a channel;*-*is the universal channel that admits every pre-release.1.x:=>=1.0.0 <2.0.0-01.2.x:=>=1.2.0 <1.3.0-01:=1.x.x:=>=1.0.0 <2.0.0-01.2:=1.2.x:=>=1.2.0 <1.3.0-0
Tilde ranges ~1.2.3 ~1.2 ~1
Allows patch-level changes if a minor version is specified; minor-level changes if not.
~1.2.3:=>=1.2.3 <1.3.0-0~1.2:=>=1.2.0 <1.3.0-0~1:=>=1.0.0 <2.0.0-0~0.2.3:=>=0.2.3 <0.3.0-0~1.2.3-beta:=>=1.2.3-beta <1.3.0-0, a pre-release channel on1.2.3.
Caret ranges ^1.2.3 ^0.2.5 ^0.0.4
Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple.
^1.2.3:=>=1.2.3 <2.0.0-0^0.2.3:=>=0.2.3 <0.3.0-0^0.0.3:=>=0.0.3 <0.0.4-0^1.0.0-rc:=>=1.0.0-rc <2.0.0-0, thercchannel of1.0.0, plus stable1.x.
Approximate ranges ~>
The pessimistic operator ~> treats the last supplied digit as the
beginning of a range and matches only within that value. It is an alias of the tilde
operator.
~>1:=>=1.0.0 <2.0.0-0~>1.2:=>=1.2.0 <1.3.0-0~>1.2.3:=>=1.2.3 <1.3.0-0
Ranges may be non-contiguous. A version might be neither greater than, less than, nor a member of a range. For example,1.2 <1.2.9 || >2.0.0has a hole from1.2.9to2.0.0. Usesatisfies(version, range)to test membership directly, and try it on the range selector.
Pre-release channels ^1.0.0-rc
A pre-release is provisional: it ranks below its release
(1.0.0-rc < 1.0.0), a step toward a version rather than a
refinement of it. So pre-releases are excluded by default (a
range of stable comparators never admits them), and a range opts in by naming the
pre-release it wants. That name is a channel: a prefix filter scoped to
the comparator's [major, minor, patch] tuple, exactly mirroring a
build channel. There is no global “include pre-releases”
switch; admission is always a property of the range.
- A range with no pre-release token anywhere admits stable versions only.
- A comparator, tilde or caret carrying a pre-release declares a channel whose prefix is its pre-release identifiers, scoped to its tuple. A pre-release candidate is admitted if and only if it is within the numeric bounds and its identifiers, compared left to right, begin with some declared channel's on the same tuple.
- Two explicit wildcards provide what a global flag used to.
X.Y.Z-*is the empty-prefix channel for one tuple (any pre-release of that release), and*-*is the universal channel (pre-releases on every tuple, the in-range equivalent of npm'sincludePrereleaseor pip's--pre). - Stable versions are unaffected by channels: once inside the numeric bounds they are always admitted.
- A tilde or caret carrying a pre-release applies its channel to the numeric lower bound it desugars to. A hyphen range keeps each endpoint's pre-release as that endpoint's channel.
^1.0.0-rc(prefixrc): matches1.0.0-rc,1.0.0-rc.1,1.0.0-rc.2; never1.0.0-beta(different channel) or1.5.0-rc(different tuple). Stable1.xstill matches.^1.0.0-rc.2(prefixrc.2): therc.2sub-tree only.^1.0.0-*:=>=1.0.0-0 <2.0.0-0+ any-pre channel on1.0.0: every pre-release of1.0.0, plus stable1.x.*-*: any version at all, stable or pre-release.
Pre-release channels are a deliberate divergence from Semantic Versioning 2.0.0 and itsnode-semverimplementation, which admit any pre-release sharing a comparator's tuple and expose a separateincludePrereleaseflag. Scoping to a prefix makes^1.0.0-alphamean the alpha channel (not “everything from alpha onward”) and folds the flag into the grammar, following NuGet's floating1.2.3-alpha.*/*-*model. Try channels on the range selector (Release train sample).
Subset selection: one rule for pre-release and build
Pre-release and build are the two labels a version can carry, and a range selects among them with the same channel/prefix rule: identifiers on a comparator, matched left-to-right as a prefix, scoped to that comparator's tuple. They differ only in their default, and that difference follows directly from precedence.
| Label | Ranks vs. its release | Default in a range | Opt in / narrow with |
|---|---|---|---|
| Pre-release | Below: provisional, on the way to the release (1.0.0-rc < 1.0.0) |
Excluded | name it (-rc), -*, or *-* |
| Build | At/above: a refinement, a specific artifact of the release (1.0.0 < 1.0.0+win) |
Included | a channel narrows (+win) |
A version is in a range when it passes every comparator's numeric test and, for whichever labels it carries, the range's channels admit them. Nothing is configured out-of-band.
Build channels ^1.0.0+win
Because build metadata carries precedence, it can also select. A build attached to a comparator, tilde or caret is a channel filter: it is orthogonal to the numeric comparison and admits only versions whose own build metadata begins with the channel's identifiers. This turns build metadata into a direct way to range over release channels and variants (signed builds, platform or architecture targets, content digests) that no registry-hosted numeric range can express.
- A comparator’s build metadata MUST be treated as a channel filter, not as part of the numeric comparison.
- The numeric comparison MUST ignore build metadata on both the comparator and the candidate version.
- A version satisfies a channel if and only if its build identifiers, compared left to right, begin with the channel’s identifiers.
- A range with no build channel matches any build (and the build-less release); a channel excludes versions that carry no build.
- A tilde or caret carrying a channel MUST apply that channel to every comparator it desugars to. A hyphen range spans two endpoints and MUST ignore their build metadata.
^1.0.0+win:=>=1.0.0+win <2.0.0-0+win: thewinchannel within^1.0.0, so it matches1.4.2+win.x64, not1.4.2+linux.x64or1.4.2.>=1.1.0+linux.x64: thelinux.x64variant at or above1.1.0.1.0.0+sha.a1b2c3: pins the exact artifact (and any narrower build beneath it).^1.0.0: no channel, so every variant of every release in range matches, build or not.
Build channels are a deliberate divergence from Semantic Versioning 2.0.0, which discards build metadata during range resolution. See Build Variants for os/arch/abi channels and Package Integrity for a channel built on content digests, and try channels live on the range selector (Build variants sample).
Relationship to Semantic Versioning 2.0.0
Semantic Versions is a peer specification, not a fork or a claimed
successor to the version line stewarded at semver.org. It carries its own
version (1.0.0) and is a spiritual successor to
Semantic Versioning 2.0.0-rc.1,
the draft before 2.0.0-rc.2 demoted the build to inert "build metadata."
A version that is valid under Semantic Versioning 2.0.0 is valid here and parses identically. The differences are additive or concern the treatment of builds:
| Topic | Semantic Versioning 2.0.0 | Semantic Versions 1.0.0 |
|---|---|---|
| Build precedence | Ignored: two versions differing only in build are equal | Participates in precedence, ranking above the plain release; a build is the antithesis of a pre-release |
| Build channels | Build discarded during range resolution | Build on a comparator/tilde/caret is a channel filter (^1.0.0+win) |
| Pre-release ranges | Any pre-release on a comparator's tuple is admitted; opt in via a separate flag | A pre-release is a prefix channel (^1.0.0-rc); wildcards -* / *-* replace the flag |
| Sets | Undefined | Defined: ordered, unordered, normal, pre-release and build sets |
| Ranges & comparators | Out of scope | Defined: comparators, comparator sets, and || unions |
| Operators | None | < > = <= >=, plus advanced tilde/caret/hyphen/x-ranges |
| Approximate operator | None | Pessimistic ~> |
| Grammar | Prose + a single regex | Published BNF for versions, ranges and comparators |
The one behavioural divergence to be aware of is build precedence: tooling that assumes builds are ignored will order build-bearing versions differently. This is deliberate. It is what lets extensions like Dependency Locks and Package Integrity carry meaning in the build. See the history for the full story.
FAQ
How does this differ from semver.org?
In short: builds participate in precedence, and Sets, Ranges, Operators and Comparators are formally defined. See Relationship to Semantic Versioning 2.0.0 above for the full comparison.
How does this relate to Package-URL (purl)?
They solve different problems and compose cleanly.
Package-URL
(ECMA-427) is an identifier: pkg:npm/react@18.2.0 standardises how
to name and locate a package across ecosystems, and is what SBOMs (CycloneDX, SPDX) and
vulnerability databases (OSV, CVE) reference. It deliberately says nothing about version
semantics (precedence, ordering or matching) and defers ranges to its separate
vers
constraint syntax.
Semantic Versions is the complementary layer: it defines what a version means,
covering precedence (including build), sets, ranges and comparators. A purl says
which package; Semantic Versions says how its versions order and match.
The source-qualified identity used here maps onto a purl one-to-one across
every ecosystem (see the mapping table under Context), so a tool can
carry both. The one thing neither purl qualifiers nor vers model
is a precedence-bearing build, which is precisely the slot our
extensions occupy.
Where is the grammar?
Backus–Naur forms for versions, ranges and comparators are published on the grammar page, alongside a reference parser.
Can I contribute, or propose an extension?
Yes. The specification, reference parser and extensions are developed in the open at vltpkg/semantic-versions. Open an issue or discussion to propose a change, report a defect, or float a new extension. Anything that rides on a source-qualified version and restored build precedence is in scope.
License
The specification text is licensed under CC BY 3.0, the reference parser under Apache 2.0. See the license page for details and attribution.