Why ^1.2.3 doesn't match 1.3.0-beta.1

^1.3.0-beta.1

To accept 1.3.0-beta.1, write ^1.3.0-beta.1. ^1.2.3 skips it even though 1.3.0-beta.1 sorts between 1.2.3 and 2.0.0, because node-semver, the range engine npm uses, lets a prerelease satisfy a range only when a comparator in the range names a prerelease with the same major, minor and patch. ^1.2.3 names none.

The rule in action

^1.3.0-beta.1 means >=1.3.0-beta.1 <2.0.0 and names a prerelease of 1.3.0, so later 1.3.0 prereleases get in and those of any other version don’t:

1.2.3         no    below the floor
1.3.0-beta.1  yes
1.3.0-beta.2  yes
1.3.0-rc.1    yes   rc sorts after beta
1.3.0         yes
1.4.0-beta.1  no    a prerelease of 1.4.0, which the range doesn't name
1.9.9         yes   the highest match
2.0.0-rc.1    no

Opting into one beta isn’t opting into the next version’s betas. node-semver’s README gives the reason: prereleases often break things between one another, and choosing 1.3.0-beta.1 accepts that risk for 1.3.0 only. Once 1.3.0 ships, the range takes releases up to 2.0.0 like any caret.

includePrerelease

node-semver’s includePrerelease option turns the rule off and treats prereleases like any other version:

semver.satisfies('1.3.0-beta.1', '^1.2.3')                               // false
semver.satisfies('1.3.0-beta.1', '^1.2.3', { includePrerelease: true })  // true
npx semver -p -r '^1.2.3' 1.2.3 1.3.0-beta.1 2.0.0-rc.1   # prints 1.2.3 and 1.3.0-beta.1

2.0.0-rc.1 stays out even then: the caret’s top bound is <2.0.0-0, below every 2.0.0 prerelease. npm has no setting for the option, so npm install and npm update always apply the rule, and so does this tool.

Gotchas

  • ^1.0.0-0 is not “every 1.x prerelease”. It’s sometimes suggested for that, npm’s own install docs among others, but it names a prerelease of 1.0.0 only: 1.3.0-beta.1 doesn’t match it.
  • * and 1.x skip prereleases too. No range admits one without naming it.
  • Pin a beta by its full version. npm install [email protected] installs exactly that prerelease; a dist-tag such as next (if the package publishes one) installs whatever the tag points to.

Cargo and Composer

Cargo works the same way: requirements exclude prereleases unless one is asked for, and "1.0.0-alpha" allows later 1.0.0 prereleases and releases, but not 1.0.1-alpha. Composer filters by stability instead: minimum-stability defaults to stable, and a flag such as @beta on one constraint lets that package in at a lower stability.

Try it in the Semver Range Checker

^1.3.0-beta.1 means >=1.3.0-beta.1 <2.0.0 — from 1.3.0-beta.1 up to, but not including, 2.0.0

5 of 8 versions match — the newest is 1.9.9.

Each version · 8 versions
VersionFits the range
1.2.3
1.3.0-beta.1
1.3.0-beta.2
1.3.0-rc.1
1.3.0
1.4.0-beta.1
1.9.9
2.0.0-rc.1

Follows node-semver, npm's range grammar: caret ^, tilde ~, x-ranges 1.2.x, hyphen ranges 1.2.3 - 2, || and comparators. A prerelease like 1.9.9-beta only matches when the range names a prerelease on the same 1.9.9 — that is why ^1.2.3 skips it. Everything runs in this page, nothing is uploaded.

Runs in your browser. Nothing you enter is sent anywhere.