^1.2.3 matches >=1.2.3 <2.0.0: version 1.2.3 and every later 1.x release, up to but not including 2.0.0. The caret keeps the major version fixed, on the SemVer 2.0.0 promise that only a major bump breaks the public API.
Against real versions
1.2.2 no below the floor
1.2.3 yes
1.2.10 yes 10 > 3: each part compares as a number
1.3.0 yes
1.9.9 yes the highest match
1.4.0-beta.1 no a prerelease the range doesn't name
2.0.0 no
2.0.0-rc.1 no
On a fresh install npm takes the version the latest tag points to if it fits the range, and otherwise the highest match: 1.9.9 here.
Gotchas
- Prereleases stay out.
1.4.0-beta.1is inside the bounds, but node-semver, npm’s range engine, admits a prerelease only when the range names one on the same1.4.0.^1.2.3never pulls in a beta by itself. - The lockfile decides first.
package-lock.jsonholds the exact version installed.npm installkeeps it while it still satisfies^1.2.3, so 1.9.9 arrives only throughnpm updateor a fresh lockfile.npm ciinstalls the lockfile exactly and stops with an error if it doesn’t matchpackage.json. - The caret is also a floor.
^1.2.3rejects1.2.2. Write the lowest version you have tested with, not an older one you hope works. - The
-0in tooling output. node-semver compiles the range to>=1.2.3 <2.0.0-0. The-0is the lowest possible prerelease of 2.0.0, so 2.0.0’s prereleases stay out even whenincludePrereleaselets other prereleases in.
Where the caret comes from
npm install pkg writes ^ plus the installed version because save-prefix defaults to ^; pnpm’s savePrefix and Yarn’s defaultSemverRangePrefix default to ^ as well. In a pnpm workspace, workspace:^ on a sibling package at 1.5.0 is written out as ^1.5.0 when you publish.
Elsewhere
- Cargo:
^1.2.3and the plain"1.2.3"both mean>=1.2.3, <2.0.0; the caret is Cargo’s default. - Composer:
^1.2.3is>=1.2.3 <2.0.0, as in npm. - In code and the shell:
semver.satisfies('1.9.9', '^1.2.3') // true
semver.satisfies('2.0.0', '^1.2.3') // false
npx semver -r '^1.2.3' 1.2.2 1.9.9 2.0.0 # prints 1.9.9