Safeguard
Supply Chain•

npm caret, tilde or exact: which version range should you use?

Which npm version range to use: caret, tilde or exact. What each allows, when to choose it, and why the lockfile matters more than package.json.

Safeguard Security Team
4 min read

Use caret for most direct npm dependencies, tilde when you only want patch updates, and an exact version for packages you cannot afford to surprise you. Whatever you write in package.json, the committed package-lock.json decides what actually installs, so treat the range as a policy and the lockfile as the record.

What each range allows

npm's own documentation defines the three families by the update type they accept. Patch ranges look like ~1.0.4, 1.0 or 1.0.x. Minor ranges look like ^1.0.4, 1 or 1.x. Major ranges, written * or x, accept everything.

Written asAcceptsTypical use
Exact, such as 1.0.4Only that versionCritical or fragile packages
Tilde, such as ~1.0.4Patch releases of 1.0Conservative, bug fixes only
Caret, such as ^1.0.4Minor and patch releases of 1Default for most packages
Star or xAny versionAlmost never appropriate

These rules assume maintainers follow semantic versioning: the first number changes for breaking changes, the middle for compatible features, the last for compatible fixes. That is a convention, not a guarantee, and a range trusts every future release that fits it.

The range is a policy, the lockfile is the record

A range answers "which future versions am I willing to accept?" A lockfile answers "which versions did we actually build with?" The npm documentation says package-lock.json describes the exact tree that was generated so later installs produce identical trees regardless of intermediate dependency updates, and that it is meant to be committed.

In CI and deploys, npm ci installs from that lockfile and exits with an error if the lockfile and package.json disagree, instead of quietly updating it. So a caret range in package.json does not mean your build floats. It means the next deliberate npm install or npm update may move the lockfile, and that move shows up as a diff you can review.

When to choose each

Pick caret when the package is well maintained, has tests you trust and you want fixes without manual bumps. This is the default npm writes for a reason.

Pick tilde when you want bug fixes but treat minor releases as changes that need a human look, for example a build tool or a framework core.

Pick exact when a package touches secrets, authentication, installs scripts, or has a history of breaking releases. You can have npm write exact versions for you with the save-exact option:

npm install --save-exact some-package

Avoid star and x on anything you ship. They accept every major version, including ones with breaking changes you have never tested.

A range accepts what the registry serves

There is a security angle to ranges. A range will accept any new release that fits, including a release published by someone who has taken over a maintainer account. The lockfile blunts this for builds that use npm ci, because the new version only enters when someone updates the lockfile and a reviewer sees it. Reading the lockfile diff in pull requests is therefore part of the control, not an optional extra.

Exact versions in package.json do not pin transitive dependencies on their own. For those, the lockfile does the work, and npm's overrides field can force a specific version of a nested package when you need to.

Where Safeguard fits

Safeguard's SBOM and dependency scanning shows which versions actually resolved in your tree and which known vulnerabilities apply to them, and you can try a free scan at gold.safeguard.sh/scan. Ranges decide what you may get; scanning tells you what you did get.

Try it yourself

Open the inventory and look at how a direct dependency expands into the transitive packages that a range quietly accepts.

Click through it at your own pace, or open it full screen.

Quick check

Does a caret range mean my CI build changes every day?

No, not if you install with npm ci. It uses the lockfile and fails if the lockfile and package.json disagree. The tree only changes when someone updates the lockfile.

Should I use exact versions everywhere?

Usually not. Exact versions slow down fixes and still leave transitive dependencies to the lockfile. Use them for the few packages where surprise is expensive.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.