Semantic Version Comparator

Compare two semantic versions and sort a list into correct semver order, including the pre-release rules that trip up ordinary string sorting.

Comparison

One dot decides whether beta10 comes after beta9

A prerelease tag is not one string. It is a list of identifiers separated by dots, and each is compared on its own. An identifier made only of digits is compared as a number. An identifier containing anything else is compared as text, one character at a time. So the dot is not decoration — it chooses the rule:

ComparisonResultWhy
1.0.0-beta.9 vs 1.0.0-beta.10 < two identifiers, the second numeric
1.0.0-beta9 vs 1.0.0-beta10 > one identifier, compared as text

Written without the dot, version 10 sorts before version 9. Across all 190 pairs of build numbers from 1 to 20, the dotted spelling gets every pair right and the run-together spelling gets 103. The two disagree on 46% of comparisons:

SpellingSorted by this page
run togetherbeta1 < beta10 < beta11 < beta2 < beta20 < beta9
with dotsbeta.1 < beta.2 < beta.9 < beta.10 < beta.11 < beta.20

Below ten the two spellings agree perfectly, which is why this hides until a project ships its tenth beta and then breaks the release ordering of everything after it. The fix costs nothing and lasts forever: give every number that means a quantity its own identifier.

Everything after the plus sign is ignored

Build metadata is not ranked last. It is not ranked at all:

ComparisonResult
1.0.0+build1 vs 1.0.0+build2=
1.0.0+aaa vs 1.0.0+zzz=
1.0.0 vs 1.0.0+anything=

That is deliberate. The field exists to identify which build an artifact came from — a commit hash, a CI number — without claiming to be newer than another artifact. It is a real trap for anyone using it as a release counter: two builds nobody would confuse are, to a resolver, the same version, and which one you get is not decided by anything written here.

And a prerelease tag makes the version smaller

1.0.0-rc.1 comes before 1.0.0, which is the opposite of how a suffix usually reads. It is why a range asking for 1.0.0 or greater quietly excludes every release candidate of 1.0.0 — they are all less than it.

ComparisonResultRule
1.0.0-alpha vs 1.0.0 < a prerelease is older than its release
1.0.0-rc.1 vs 1.0.0 < even the last one
2.0.0-alpha vs 1.9.9 > but the numbers are read first
1.0.0-1 vs 1.0.0-alpha < a number ranks below a word
1.0.0-alpha vs 1.0.0-alpha.1 < and a shorter list below a longer one

Two of those catch people out for the same underlying reason. A bare number ranks below a word, so 1.0.0-1 is older than 1.0.0-alpha. And a shorter list of identifiers ranks below a longer one that matches so far, so 1.0.0-alpha is older than 1.0.0-alpha.1.

Put together, sorting a shuffled list here reproduces the published worked example exactly: 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

Two things this page accepts that the grammar does not

InputHereStrict
1.0.0 accepted accepted the plain form
v1.0.0 accepted rejected how git tags are written
01.0.0 accepted rejected leading zeros are forbidden
1.0 rejected rejected all three numbers are required
1.0.0.0 rejected rejected and only three
1.0.0- rejected rejected an empty prerelease
1.2.3-alpha+build accepted accepted both suffixes at once

The two are not the same kind of looseness. A leading v is forbidden by the specification but is how nearly every git tag is written, so accepting it removes a chore and introduces no ambiguity. Leading zeros are genuine leniency: 01.0.0 is read as 1.0.0 here and rejected outright by a strict parser. If you are checking whether a string is well-formed rather than what it sorts as, that difference is the whole answer.

How to use

  1. Enter two versions to compare, or paste a list to sort.
  2. Read which is higher and why.
  3. Check how pre-release tags affect the order.
  4. Use the range checker to test whether a version satisfies a constraint.

Frequently asked questions

What do the three numbers mean?

Major, minor and patch. Patch is for backward-compatible bug fixes, minor for backward-compatible new features, and major for changes that break existing users. The contract is what gives the numbers meaning — a major bump is a promise that something will break.

Why can't I just sort versions as strings?

Because string sorting compares digit by digit, so 1.10.0 sorts before 1.9.0 — the character 1 precedes 9. Semver compares each component numerically, which gives the order people expect.

How do pre-release versions sort?

Below the release they precede, so 1.0.0-alpha comes before 1.0.0. Among themselves, dot-separated identifiers are compared left to right, numerically where numeric and alphabetically otherwise, and a shorter set of identifiers sorts lower. So alpha precedes beta, and beta.2 precedes beta.10.

What is build metadata?

Anything after a plus sign. It is ignored entirely when comparing versions, so 1.0.0+build1 and 1.0.0+build2 are considered equal. It exists to record provenance, not to distinguish releases.

What does the caret in a dependency range mean?

Compatible with the given version — allowing anything up to but not including the next major release. The tilde is narrower, generally allowing only patch updates. Both rest entirely on the package author having honoured semver, which is not guaranteed.

What does a zero major version mean?

That the API is unstable and anything may change. Under the specification, 0.x releases carry no compatibility promise at all, which is why tools treat a caret range on a 0.x version much more conservatively than on a 1.x.

🔒 This tool runs entirely in your browser. Nothing you enter is uploaded, logged, or stored.