A caret range allows minor and patch updates, a tilde range only patch updates: ^1.2.3 means >=1.2.3 <2.0.0, and ~1.2.3 means >=1.2.3 <1.3.0. Both start at the version you wrote; they differ in how far up they reach.
Side by side
The same five releases against each range:
version ^1.2.3 ~1.2.3
1.2.3 yes yes
1.2.9 yes yes
1.3.0 yes no
1.9.9 yes no
2.0.0 no no
The tool below starts on ~1.2.3. Pick the ^1.2.3 chip to watch 1.3.0 and 1.9.9 join.
Where they meet
The caret keeps the leftmost non-zero number fixed, so below 1.0.0 it narrows. ^0.2.3 is >=0.2.3 <0.3.0, the same as ~0.2.3. ^0.0.3 is >=0.0.3 <0.0.4, tighter than ~0.0.3, which reaches <0.1.0. With only a major written, the tilde widens instead: ~1 is >=1.0.0 <2.0.0, the same as ^1.
Which one npm writes
npm install pkg saves a caret, because the save-prefix setting defaults to ^. npm config set save-prefix='~' makes later installs write a tilde, and --save-exact (-E) writes the bare version.
The range is only consulted when versions get resolved. package-lock.json records the exact version installed; npm install keeps it as long as it still satisfies package.json, and npm ci installs exactly what the lockfile lists. npm update is what moves a dependency up within its range, so on a locked project the choice of ^ or ~ decides how far npm update may go.
npx semver -r '~1.2.3' 1.2.9 1.3.0 # prints 1.2.9
Other package managers
- pnpm defaults its
savePrefixsetting to^, and Yarn (2 and later) itsdefaultSemverRangePrefix;yarn add -Twrites~andyarn add -Eno prefix. Both resolve npm ranges with the samesemverpackage. - Cargo reads
^and~the same way, but a bare"1.2.3"inCargo.tomlmeans^1.2.3. Inpackage.json, bare1.2.3is that version exactly. - Composer agrees on
^1.2.3and~1.2.3, but reads~1.2as>=1.2 <2.0.0: the last number written may rise. npm reads~1.2as>=1.2.0 <1.3.0.