Regex for a Semantic Version (semver 2.0.0)
The pattern semver.org itself suggests, with its five capture groups explained, a test table built from the specification's own examples, and a second, simpler pattern for release versions only.
Official semver.org pattern (numbered groups)
Rule: A valid version as defined by the Semantic Versioning 2.0.0 grammar: MAJOR.MINOR.PATCH with no leading zeros, optional -prerelease and optional +build.
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ Open in tester Loads the pattern with sample text, JavaScript flavor.
Parts of the pattern
(0|[1-9]\d*)- Group 1, major: either "0" or a number that does not start with 0. Repeated for group 2 (minor) and group 3 (patch), separated by escaped dots.
(?:-( … ))?- Optional pre-release: a hyphen, then group 4 holding dot-separated identifiers.
0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*- One pre-release identifier: a numeric identifier without leading zeros, or an alphanumeric one that contains at least one letter or hyphen (so "0a" and "-" are allowed but "0123" is not).
(?:\+( … ))?- Optional build metadata: a plus sign, then group 5 with dot-separated identifiers of letters, digits and hyphens. Leading zeros are allowed here.
Test cases
27 of 27 rows agree with the rule.
| Input | Rule says | Pattern says | Result | Note |
|---|---|---|---|---|
0.0.4 | valid | match | pass | Zero is a valid number. |
1.2.3 | valid | match | pass | Plain release. |
10.20.30 | valid | match | pass | Multi-digit numbers. |
1.0.0-alpha | valid | match | pass | Spec example. |
1.0.0-alpha.1 | valid | match | pass | Spec example. |
1.0.0-0.3.7 | valid | match | pass | Spec example: numeric identifiers 0, 3 and 7. |
1.0.0-x.7.z.92 | valid | match | pass | Spec example. |
1.0.0-x-y-z.-- | valid | match | pass | Spec example: hyphens are identifier characters. |
1.0.0-alpha+001 | valid | match | pass | Spec example: build metadata may have leading zeros. |
1.0.0+20130313144700 | valid | match | pass | Spec example. |
1.0.0-beta+exp.sha.5114f85 | valid | match | pass | Spec example. |
1.0.0+21AF26D3----117B344092BD | valid | match | pass | Spec example. |
1.0.0-0a | valid | match | pass | Alphanumeric identifier that starts with 0 is allowed; the no-leading-zero rule is for numeric identifiers. |
1 | invalid | no match | pass | Needs MAJOR.MINOR.PATCH. |
1.2 | invalid | no match | pass | Missing patch. |
1.2.3.4 | invalid | no match | pass | Four numbers. |
01.2.3 | invalid | no match | pass | Leading zero in major. |
1.02.3 | invalid | no match | pass | Leading zero in minor. |
1.2.03 | invalid | no match | pass | Leading zero in patch. |
1.2.3-0123 | invalid | no match | pass | Numeric pre-release identifier with a leading zero. |
1.2.3- | invalid | no match | pass | Empty pre-release. |
1.2.3+ | invalid | no match | pass | Empty build metadata. |
1.0.0-alpha..1 | invalid | no match | pass | Empty identifier between dots. |
1.0.0-alpha_beta | invalid | no match | pass | Underscore is not an allowed identifier character. |
v1.2.3 | invalid | no match | pass | The semver FAQ: "v1.2.3" is not a semantic version; it is a tag name that contains one. |
1.2.3␣ | invalid | no match | pass | Trailing space. |
1.2.3\n | invalid | no match | pass | Trailing newline: rejected by JavaScript's $; PCRE2, Python and Java's $ would accept it. |
Release versions only (X.Y.Z)
Rule: MAJOR.MINOR.PATCH with each number "0" or without leading zeros; no pre-release or build metadata.
/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/ Open in tester Loads the pattern with sample text, JavaScript flavor.
Parts of the pattern
(0|[1-9][0-9]*)- A number with no leading zero, captured as major, minor and patch.
Test cases
11 of 11 rows agree with the rule.
| Input | Rule says | Pattern says | Result | Note |
|---|---|---|---|---|
0.0.0 | valid | match | pass | All zero. |
1.2.3 | valid | match | pass | Plain. |
12.345.6789 | valid | match | pass | Multi-digit. |
1.2.3-alpha | invalid | no match | pass | Pre-release: valid semver but rejected here by design. |
1.2.3+build | invalid | no match | pass | Build metadata: valid semver but rejected here by design. |
1.2 | invalid | no match | pass | Missing patch. |
1.2.3.4 | invalid | no match | pass | Four parts. |
00.1.2 | invalid | no match | pass | Leading zero. |
1.2.03 | invalid | no match | pass | Leading zero. |
v1.2.3 | invalid | no match | pass | v prefix. |
-1.2.3 | invalid | no match | pass | Negative number. |
How it works
The pattern in the first variant is copied from the Semantic Versioning FAQ ("Is there a suggested regular expression (RegEx) to check a SemVer string?"), which offers two: one with named groups and one with numbered groups that it says is compatible with ECMAScript, PCRE, Python and Go. This page uses the numbered one. The five capture groups are major, minor, patch, pre-release and build metadata.
The grammar rules it encodes come straight from the specification: major, minor and patch are non-negative integers without leading zeros; a pre-release is a hyphen followed by dot-separated identifiers of [0-9A-Za-z-] that must not be empty, where numeric identifiers must not have leading zeros; build metadata is a plus sign followed by dot-separated non-empty identifiers of the same characters (leading zeros allowed).
The trickiest part is the alternation 0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*. A purely numeric identifier must be 0 or start with 1-9. An identifier containing any letter or hyphen is alphanumeric and may start with a digit, including 0: "0a" is valid and "0123" is not. The third alternative requires at least one non-digit, which is how it avoids matching "0123".
Every example version printed in the specification is in the test table (spec section on pre-release and build metadata), together with the near misses listed in the FAQ and the grammar: leading zeros, empty identifiers, missing parts.
Known false positives
- The pattern checks syntax only. 99999999999999999999.0.0 passes, because the grammar has no upper bound on a numeric identifier. If you convert the parts to numbers in your program, decide what you do about values too large for the type you convert to.
- It does not say a version is sensible: 0.0.0 is valid, and so is 1.0.0-0 (a pre-release).
Known false negatives
- Tag names like v1.2.3, version ranges (^1.2.3, >=1.0.0 <2.0.0), and npm-style loose versions (1.2, 1) are rejected. They are not semantic versions: the semver FAQ says "v1.2.3" is a tag name that contains a semantic version.
- Four-part versions such as 1.2.3.4 (common on Windows) are rejected: semver has exactly three numeric parts.
Flavor notes
- The pattern avoids lookaround and backreferences, so it compiles in JavaScript, PCRE2 and Go. The specification's FAQ says the numbered-group version works in ECMAScript, PCRE, Python and Go; this site runs it on JavaScript, PCRE2 and Go, and the table is executed there.
- \d is used in the official pattern. In Python str patterns \d matches any Unicode decimal digit and in PCRE2 with UCP it matches \p{Nd}; for strict ASCII behaviour in those flavors, replace \d with [0-9] (the release-only variant already does).
- Trailing newline: JavaScript and Go's $ match only at the very end of the input; PCRE2 (default), Python and Java's $ also match before a final newline. Use \z, \Z, fullmatch() or matches() when validating.
Why not regex here?
If you need to compare versions (which is newer, does this satisfy a range), a regex only splits the string. Precedence rules live in the specification (pre-release versions sort lower than the release, numeric identifiers compare as numbers and alphanumeric ones in ASCII order); use a semver library for that and use the regex only to validate or to split.
How to use
- Copy the pattern for the variant that fits your rule. Variants differ in strictness, and the rule line says exactly what each accepts.
- Check how it is anchored: the patterns use
^and$to test a whole string. To find the same thing inside longer text, remove the anchors (and add word-boundary or lookaround checks) and re-run the cases. - If your language is not JavaScript, read Flavor notes and change
$to\zor use a full-match function. - Open it in the tester to see the explanation of each token and try your own inputs.
Worked examples
Split a version into parts
Run the official pattern in the tester with the g flag off and read groups 1 to 5: for 1.0.0-beta+exp.sha.5114f85 they are 1, 0, 0, beta and exp.sha.5114f85.
Reject the tag name
Strip a leading "v" before testing if your input is a Git tag: the semver FAQ explains that the version is "1.2.3" and the tag name is "v1.2.3".
Limits & gotchas
- Matches the 2.0.0 grammar as read on 2026-10-02. It does not know about any later revision.
- It does not enforce any maximum length, so very long inputs produce long matches. Check the length first if input is untrusted.
FAQ
Is "v1.2.3" a valid semantic version?
No. The Semantic Versioning FAQ says "v1.2.3" is not a semantic version; prefixing with v is a common way to indicate a version number, as in a Git tag name where the semantic version is "1.2.3".
Why is 1.2.3-0123 invalid but 1.0.0-0a valid?
Numeric pre-release identifiers must not include leading zeroes. "0123" is numeric, so it is rejected. "0a" contains a letter, so it is alphanumeric and the rule does not apply.
Do leading zeros matter in build metadata?
No. Build metadata identifiers may be all digits with leading zeros: 1.0.0-alpha+001 is one of the specification's own examples. Build metadata is also ignored when determining version precedence.
Where do the capture groups point?
Group 1 is major, 2 minor, 3 patch, 4 the pre-release string (or unmatched) and 5 the build metadata (or unmatched).
Sources
- semver.org: Semantic Versioning 2.0.0 Used for: Grammar, no leading zeros, pre-release and build rules, the suggested regular expressions, example versions.
- MDN: Input boundary assertion: ^, $ Used for: ^ and $ are the start and end of input, or of each line with the m flag.
- MDN: Character class escape: \d, \D, \w, \W, \s, \S Used for: \d is [0-9]; \w is letters, digits and underscore; \s is whitespace plus line terminators.
- PCRE2: pcre2pattern Used for: Syntax and semantics: groups, named groups, lookbehind rules, atomic groups, possessive quantifiers, \d \s \w with and without UCP, dollar and newline handling, \A \Z \z.
- Python docs: re: Regular expression operations (3.13) Used for: Syntax, (?P<name>), atomic groups and possessive quantifiers (3.11+), fixed-length lookbehind, Unicode \d \s \w, $ before trailing newline, \A \Z, re.sub replacement syntax, inline flags at start only (3.11+).
- Oracle (Java SE 21): java.util.regex.Pattern Used for: Construct table, \d \s \w without UNICODE_CHARACTER_CLASS, possessive and atomic constructs, named groups, line terminators and $, \A \Z \z.
- Go: regexp/syntax Used for: Full syntax table; no lookaround or backreferences; \d \s \w are ASCII-only; $ is \z; (?P<name>) and (?<name>); repetition limit 1000; \A and \z.
- Google RE2: RE2 Syntax (wiki) Used for: RE2 syntax with explicit NOT SUPPORTED markers: lookaround, backreferences, possessive quantifiers, \Z, atomic groups.
Every document above was opened and read on 2026-10-02. Documentation changes; if a page here disagrees with the current docs, trust the docs and tell us.