Quick answer
The official semantic versioning regex, published on semver.org, matches major.minor.patch with an optional -prerelease and +build part, and captures each piece in a named group. Numbers cannot have leading zeros, so 1.0.0, 2.10.3, 1.0.0-alpha.1 and 1.0.0+20130313144700 match, while 1.0, 01.0.0, v1.0.0 and 1.0.0-01 do not. Strip a leading v before matching.
- Tested examples
- Token-by-token breakdown
- Code in 5 languages
- Live tester
How to use the semantic version regex
- 1Copy the pattern with Copy pattern in the tester, or take a ready-made snippet for your language from the code section below.
- 2Check the sample lines in Test string: every line the semantic version regex accepts is highlighted, rejected lines stay plain.
- 3Paste your own values one per line to see which pass, and read Explanation for what each part of the pattern does.
- 4Open the Code tab for JavaScript, Python, Go, C#, PHP or Dart with the pattern translated and escaped.
- 5Validate again on the server: client-side checks give users quick feedback, not security.
How does the semantic version regex work?
Read it left to right. Each piece below matches one part of the input:
(?<major>0|[1-9]\d*)The major version: 0, or a number without a leading zero.\.(?<minor>0|[1-9]\d*)A dot and the minor version, same rule.\.(?<patch>0|[1-9]\d*)A dot and the patch version.(?:-(?<prerelease>…))?Optional prerelease after a hyphen: dot-separated identifiers. Numeric ones cannot have leading zeros (alpha.1, rc.2, 0.3.7).(?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?Optional build metadata after a plus: dot-separated alphanumerics. Leading zeros are fine here.^ … $Anchored, so nothing can come before or after the version.
Which strings does the semantic version regex match?
The whole input must match, from the first character to the last. Every row below is checked against the pattern by our automated test suite before each release.
| Input | Matches? | Why |
|---|---|---|
1.0.0 | Yes | A release. |
2.10.3 | Yes | Multi-digit numbers. |
0.1.0 | Yes | Major version zero, for initial development. |
1.0.0-alpha | Yes | A prerelease. |
1.0.0-alpha.1 | Yes | Prerelease with a numeric identifier. |
1.0.0-0.3.7 | Yes | All-numeric prerelease identifiers. |
1.0.0+20130313144700 | Yes | Build metadata only. |
1.0.0-beta+exp.sha.5114f85 | Yes | Prerelease and build metadata. |
1.0 | No | All three numbers are required. |
01.0.0 | No | No leading zeros in version numbers. |
v1.0.0 | No | A v prefix is a tag convention, not part of SemVer. Strip it first. |
1.0.0-01 | No | Numeric prerelease identifiers cannot have leading zeros. |
1.0.0- | No | An empty prerelease. |
1.2.3.4 | No | Four numbers. |
How do I compare two semantic versions?
Not with string comparison: "1.10.0" < "1.9.0" is true alphabetically. Compare the captured numbers in order, then apply the prerelease rule from Semantic Versioning 2.0.0: a version with a prerelease is lower than the same version without one, and prerelease identifiers compare numerically when both are numbers and alphabetically otherwise. So:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0Build metadata (+…) is ignored when comparing. In practice, use a library that has these rules right: semver on npm, packaging.version in Python, golang.org/x/mod/semver in Go.
Why does this pattern use named groups?
So the code that reads it says what it means: match.groups.major instead of match[1]. It is the same regex semver.org publishes, which makes it easy to verify. The code export translates the groups for each language: Python and Go use (?P<major>…), and PHP refers to them by number in replacements.
To accept a leading v, as in Git tags, add v? after the ^. For npm-style ranges (^1.2.0, ~1.2), do not extend the regex: those are a separate grammar, and the semver package parses them.
How do I use the semantic version regex in JavaScript, Python, Go, C# and PHP?
The pattern is translated for each engine rather than pasted as is: named groups, anchors, escapes, shorthand classes and flags are adjusted so each snippet behaves as closely to the JavaScript version as its engine allows. Differences that remain (Go's RE2 has no lookarounds or backreferences, for example) are called out under each one. The Code tab in the tester above also exports Dart, and updates as you edit the pattern.
JavaScript and TypeScript
const regex = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
function isValid(value) {
return regex.test(value);
}
console.log(isValid("1.0.0"));Python
import re
pattern = re.compile(r"^(?P<major>0|[1-9][0-9]*)\.(?P<minor>0|[1-9][0-9]*)\.(?P<patch>0|[1-9][0-9]*)(?:-(?P<prerelease>(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z\-][0-9a-zA-Z\-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z\-][0-9a-zA-Z\-]*))*))?(?:\+(?P<build>[0-9a-zA-Z\-]+(?:\.[0-9a-zA-Z\-]+)*))?\Z")
def is_valid(value: str) -> bool:
return pattern.search(value) is not None
print(is_valid("1.0.0"))- \d and \w are written as [0-9] and [A-Za-z0-9_] because in Python they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. Python's $ also matches before a trailing newline, so the pattern uses \Z instead.
Go
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*))*))?(?:\+(?P<build>[0-9a-zA-Z\-]+(?:\.[0-9a-zA-Z\-]+)*))?$`)
fmt.Println(re.MatchString("1.0.0"))
}C#
using System;
using System.Text.RegularExpressions;
// The timeout stops a pathological input from hanging the thread (ReDoS).
var regex = new Regex(@"^(?<major>0|[1-9][0-9]*)\.(?<minor>0|[1-9][0-9]*)\.(?<patch>0|[1-9][0-9]*)(?:-(?<prerelease>(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z\-][0-9a-zA-Z\-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z\-][0-9a-zA-Z\-]*))*))?(?:\+(?<build>[0-9a-zA-Z\-]+(?:\.[0-9a-zA-Z\-]+)*))?\z", RegexOptions.None, TimeSpan.FromSeconds(1));
Console.WriteLine(regex.IsMatch("1.0.0"));- \d and \w are written as [0-9] and [A-Za-z0-9_] because in .NET they also match digits and letters from other scripts.
- JavaScript's $ only matches at the very end of the string. .NET's $ also matches before a trailing newline, so the pattern uses \z instead.
PHP
<?php
$pattern = '/^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z\-][0-9a-zA-Z\-]*))*))?(?:\+(?<build>[0-9a-zA-Z\-]+(?:\.[0-9a-zA-Z\-]+)*))?$/D';
function isValid(string $value, string $pattern): bool
{
return preg_match($pattern, $value) === 1;
}
var_dump(isValid('1.0.0', $pattern));- The D modifier makes $ match only at the very end, like JavaScript (PCRE's $ also matches before a trailing newline).
Which related regex patterns are useful?
- Regex for UUIDs (Versions 1–8): 8-4-4-4-12 hex layout with a real version digit (1–8) and the standard variant.
- Regex for Numbers Only: Digits only, plus variants for signs, decimals and thousands separators.
- Regex for URL Slugs: Lowercase letters and digits separated by single hyphens, like regex-tester.
- Regex for Dates in yyyy-mm-dd Format: ISO 8601 calendar dates: four-digit year, month 01–12, day 01–31.
Browse all 25 patterns in the regex pattern library, or build your own in the regex tester.
Frequently asked questions
Last updated by the Playxoft engineering team.