Skip to content

Module Versioning — Specification

Table of Contents

  1. Scope
  2. Version String Grammar
  3. Canonical Form
  4. Version Comparison
  5. Pseudo-Version Grammar
  6. Major Version Suffix Rule
  7. +incompatible Rule
  8. go.mod Directives Related to Versioning
  9. Minimum Version Selection Definition
  10. Authoritative References

Scope

This file paraphrases the normative rules that govern Go module versions: how a version string is shaped, how two versions compare, how the toolchain assembles a pseudo-version, and what relationship between go.mod and the module path the toolchain enforces. It is a reference, not a tutorial; for tutorial material see junior.md through professional.md.

The authoritative sources are the Go modules reference at go.dev/ref/mod and the upstream semver.org 2.0.0 specification. Where the two differ, go.dev/ref/mod governs Go-specific behaviour.


Version String Grammar

A canonical version in Go has the shape:

version    := "v" major "." minor "." patch [ "-" prerelease ] [ "+" build ]
major      := decimal              ; no leading zeros except "0"
minor      := decimal
patch      := decimal
prerelease := identifier ( "." identifier )*
build      := identifier ( "." identifier )*
identifier := alphanumeric ; numeric identifiers must not have leading zeros
alphanumeric := [0-9A-Za-z-]+ ; with at least one non-digit if numeric leading-zero would otherwise apply
decimal    := "0" | [1-9] [0-9]*

Rules in prose:

  • Mandatory leading lowercase v.
  • Three numeric components separated by ..
  • No leading zeros in any numeric component (except a literal 0).
  • Optional -prerelease block. Identifiers separated by .. Each identifier is alphanumeric (with - allowed) or pure numeric (no leading zeros).
  • Optional +build metadata. Same identifier shape, but ignored for ordering.
  • No whitespace anywhere.

Examples of valid versions

  • v1.0.0
  • v0.1.0
  • v2.5.7
  • v1.0.0-alpha
  • v1.0.0-alpha.1
  • v1.0.0-0.3.7
  • v1.0.0-x.7.z.92
  • v1.0.0+20130313144700
  • v1.0.0-beta+exp.sha.5114f85
  • v1.5.0+incompatible (special, see below)

Examples of invalid versions

  • 1.0.0 — missing v
  • V1.0.0 — capital V
  • v01.0.0 — leading zero
  • v1.0 — only two numeric components
  • v1.0.0.0 — four numeric components
  • v1.0.0- — trailing - with no identifier
  • v1.0.0-alpha..1 — empty identifier
  • v 1.0.0 — whitespace

Canonical Form

Every version has exactly one canonical form. The toolchain rejects non-canonical forms in go.mod. Canonicalisation rules:

  • All numeric components present (v1 and v1.0 are not canonical; v1.0.0 is).
  • Lowercase v.
  • No leading zeros.
  • No redundant +build metadata when ordering is unaffected — but Go preserves whatever was given.

The function golang.org/x/mod/semver.Canonical(v) returns the canonical form of an input string (or empty if invalid).


Version Comparison

Defined by golang.org/x/mod/semver.Compare(v, w), which returns -1, 0, or +1. The algorithm:

  1. Strip the leading v.
  2. Compare MAJOR, MINOR, PATCH numerically in order. If any differs, return that comparison.
  3. If all three are equal:
    • If neither has a pre-release, return equal.
    • If exactly one has a pre-release, the one with a pre-release is less.
    • Otherwise compare pre-release components left-to-right:
      • If both are numeric, compare numerically.
      • If both are alphanumeric, compare lexicographically.
      • If types differ, numeric is less than alphanumeric.
      • If one runs out of components first, the shorter one is less.
  4. +build metadata never affects ordering.

Worked comparisons

v w Result
v1.0.0 v1.0.0 0
v1.0.0 v1.0.1 -1
v1.0.0 v0.999.999 +1
v1.0.0-alpha v1.0.0 -1
v1.0.0-alpha v1.0.0-alpha.1 -1
v1.0.0-alpha.1 v1.0.0-alpha.10 -1 (numeric)
v1.0.0-alpha.10 v1.0.0-beta -1 (alphanumeric alpha < beta)
v1.0.0-1 v1.0.0-alpha -1 (numeric < alphanumeric)
v1.0.0+a v1.0.0+b 0 (build metadata ignored)
v2.0.0+incompatible v2.0.0 0

Pseudo-Version Grammar

A pseudo-version is generated by cmd/go for any commit that is not the target of a semver tag. The grammar:

pseudo-version := "v" base "-" timestamp "-" hash

Where base depends on context:

  • If no semver tag is reachable from the commit's history: 0.0.0.
  • If a tag vX.Y.Z is reachable and the commit comes after it on the same major: X.Y.(Z+1)-0.
  • If a tag vX.Y.Z-pre is reachable: X.Y.Z-pre.0.

timestamp is the commit time in UTC formatted as YYYYMMDDHHMMSS.

hash is the first 12 lowercase hex digits of the commit hash.

Pseudo-version examples

Base tag Pseudo-version
(none) v0.0.0-20240612103515-abc123def456
v1.5.0 v1.5.1-0.20240612103515-abc123def456
v1.5.0-rc.1 v1.5.0-rc.1.0.20240612103515-abc123def456
v2.0.0 (with /v2 path) v2.0.1-0.20240612103515-abc123def456

Ordering with pseudo-versions

Pseudo-versions interleave with normal versions according to the same comparison rules:

v1.5.0 < v1.5.1-0.<earlier-ts>-... < v1.5.1-0.<later-ts>-... < v1.5.1

The -0. pre-release marker keeps pseudo-versions strictly between the preceding tag and the next patch.

When the toolchain emits a pseudo-version

When the user references a Git ref that is not a canonical tag:

  • A commit hash (@abcdef1).
  • A branch name (@main, @master, @HEAD).
  • A non-semver tag (@release-2024).
  • An empty version (@) — equivalent to @latest but for an untagged repo.

Major Version Suffix Rule

For any module at major version N >= 2:

  1. The module path declared by the module directive in go.mod must end with /vN.
  2. Every import of any package within the module must use the /vN-suffixed path.
  3. The Git tag is vN.X.Y (without the suffix; the suffix is in the module path, not the tag name).

For modules at major version 0 or 1, the path must not include a /vN suffix.

The toolchain enforces these rules at build time:

  • A go.mod with module github.com/foo/bar tagged v2.0.0 (without +incompatible opt-out) is rejected: "module declares its path as github.com/foo/bar but was required as github.com/foo/bar/v2".
  • A go.mod with module github.com/foo/bar/v2 tagged v1.0.0 is rejected: "v1.0.0 invalid: should be v2 or later, since the module declares the path with /v2 suffix".

+incompatible Rule

A module that has tags vN.X.Y for N >= 2 but does not declare a /vN suffix is referenced with +incompatible. Specifically:

  • The toolchain searches Git tags for versions vN.X.Y where N matches the requirement.
  • If found, but the module path declared in go.mod does not end with /vN, the version is annotated +incompatible in go.mod and go.sum.
  • +incompatible versions interoperate as a single import path with the v0.x.y and v1.x.y of the same module (no multi-major coexistence).

The marker is informational. It states: "the maintainer did not adopt SIV for this major; expect breakage if you upgrade silently."


Directive Syntax Meaning
module module path Declares the module path, including any /vN suffix for N >= 2.
go go MAJOR.MINOR Declares the minimum Go language version. Affects lazy-loading behaviour at 1.17+.
toolchain toolchain go1.21.0 (Optional, since Go 1.21.) Pins a specific toolchain.
require require path version Pins a dependency. May be a real version or a pseudo-version.
replace replace path [version] => path [version] Redirects an import path. Only the main module's replace is honoured.
exclude exclude path version Forbids a specific version from MVS. Only the main module's exclude is honoured.
retract retract version or retract [v1, v2] Marks a version (or range) of this module as not-recommended.

Indirect dependencies are marked with // indirect after the require line. From go 1.17 on, every transitively-required module appears as an explicit require (often // indirect).


Minimum Version Selection Definition

The cmd/go documentation defines MVS as follows. Given:

  • M = the main module.
  • Reqs(m) = the function returning the requirement list of module-version m.
  • Vers(p) = the set of available versions of module path p.

Define Build(M) as the smallest set S of module-versions such that:

  1. M ∈ S.
  2. For every m ∈ S and every (p, v) ∈ Reqs(m): there exists some m' ∈ S with m'.Path = p and m'.Version >= v.
  3. For every path p such that some module in S requires path p: exactly one module-version m ∈ S with m.Path = p.
  4. The version m.Version for each path p is the maximum v such that (p, v) ∈ Reqs(x) for some x ∈ S.

Equivalently, Build(M) is computed by the algorithm in professional.md: repeatedly take the highest required version for each path until the queue is empty.

The result is unique for any valid input — there is no choice point and no backtracking.

replace and exclude directives in the main module's go.mod modify the inputs to MVS:

  • replace p [v1] => q [v2] rewrites every requirement on p (or (p, v1) if a version is given) into a requirement on (q, v2).
  • exclude p v removes (p, v) from the input space; if a Reqs entry would force it, MVS picks the next-higher available version.

Authoritative References

  • The Go modules reference at go.dev/ref/mod. Sections: "Version queries", "Pseudo-versions", "Module compatibility and semantic versioning", "Resolving a package to a module", "Minimal version selection".
  • The upstream semver.org 2.0.0 specification. Go applies it strictly with the additional v prefix and SIV requirements.
  • The Go blog post "Go Modules: v2 and Beyond" — informal but clarifying.
  • The package golang.org/x/mod/semver — the canonical implementation of version parsing and comparison.
  • The package golang.org/x/mod/module — module path validation and pseudo-version helpers.

For implementation details (MVS, proxy protocol, go.sum computation), see professional.md.