A caret (^) before a version number allows updates that should not break your code, usually new minor and patch releases. A tilde (~) allows a narrower set, usually patch releases only. These signs tell your package manager which versions are acceptable, which affects what gets installed and how fast fixes arrive.
TL;DR
- A version range is a rule for which versions your app will accept.
- Caret is the wider rule. Tilde is the narrower rule. An exact version is the narrowest.
- Wide ranges pick up fixes automatically, but can also pull in surprises.
- The lockfile records what was actually installed, which is what scanners check.
- Know your ranges so you know how your security fixes will arrive.
What is a version range?
Definition: A version range is a rule written next to a package name that says which versions of that package your project will accept. Your package manager picks a specific version that fits the rule.
When you add a package to a project, you do not always say "give me exactly version 4.17.21." Often you say "give me something close to 4.17.21." The first approach is predictable but you have to update by hand. The second approach lets small updates arrive without any work, but it trades some control for convenience.
Think of ordering coffee. "A large latte with oat milk" is exact. "Any large coffee" is a range. With the second order, you get a good result most days, but you do not control the details.
If you want a refresher on version numbers themselves, our guide on whether to upgrade to the latest version explains the three part scheme: major, minor, and patch.
What do the common signs mean?
The npm documentation for the manifest file lists the forms a version range can take. It says dependencies are specified as an object that maps a package name to a version range, and the range is a string of one or more descriptors. Here are the ones you will see most.
| What you see | In plain words | How wide it is |
|---|---|---|
1.2.3 | Exactly this version | Narrowest |
~1.2.3 | Roughly this version, patch updates only | Narrow |
^1.2.3 | Compatible with this version, minor and patch updates | Wider |
1.2.x | Any patch of 1.2 | Narrow |
1.x | Any minor of 1 | Wider |
* | Any version at all | Widest |
latest | Whatever is tagged latest | Widest, and moves |
The detailed rules come from the node-semver documentation, which npm uses. Here is what it says, translated into simple terms.
- A tilde range allows patch level changes if a minor version is given. So the tilde form of 1.2.3 accepts anything from 1.2.3 up to, but not including, 1.3.0.
- A caret range allows changes that do not modify the left most non zero number. For versions 1.0.0 and above, that means minor and patch updates. So the caret form of 1.2.3 accepts anything from 1.2.3 up to, but not including, 2.0.0.
Here are the rules written as ranges, for readers who like exact notation:
~1.2.3 means >=1.2.3 and below 1.3.0
^1.2.3 means >=1.2.3 and below 2.0.0
~1 means >=1.0.0 and below 2.0.0
What changes for versions that start with zero?
Careful here. The node-semver documentation says that for versions numbered 0.x, the caret allows patch updates for 0.1.0 and above, and no updates at all for 0.0.x. In its words, many authors treat a 0.x version as if the x were the major breaking change indicator.
So a caret on version 0.2.5 accepts 0.2.6 but not 0.3.0. And a caret on 0.0.4 accepts only 0.0.4. The same sign behaves differently depending on the number, which surprises many people.
The SemVer specification itself says that version zero is for initial development and that anything may change at any time. For these packages, even a patch can behave unexpectedly, so read release notes.
Why does this matter for security?
Because your range decides how fixes reach you. There are two sides.
The good side. If your range accepts new patch and minor versions, a fix released in a patch will be picked up the next time someone installs without a lockfile in the way, or when you refresh the lockfile. You get security fixes with less effort.
The risky side. A wide range means you may get a new version you did not test. A broken or even a harmful release could be pulled in automatically. And a range of "any version" or "latest" gives you no control at all.
The lockfile is what ties this together. The npm documentation describes the lockfile as recording the exact tree that was generated, so that later installs are identical, whatever updates happened in between. It also says the audit command normally requires a lockfile, and that bypassing it can give different results each run because the tree is rebuilt each time.
So there are two layers:
- The range in your manifest says what is allowed.
- The lockfile says what you actually have.
Scanners check what you actually have. That means a scan result can differ from what you expect if you only read the manifest. For a deeper look at the lockfile side, see best practices for npm lockfile security.
Which range should I choose?
There is no single right answer. Use this as a guide.
| Situation | Reasonable choice | Why |
|---|---|---|
| Normal library in a team app with tests | Caret, with a committed lockfile | Gets fixes, stays predictable |
| Very sensitive package, such as one that handles login | Exact version, updated deliberately | Maximum control |
| Version zero package | Exact version, or tilde with care | Small changes can break things |
| Anything with star or latest | Avoid | You do not control what arrives |
| Packages taken straight from a web address or repository | Pin to a specific commit | The npm docs support a commit reference for exactly this |
Whatever you pick, keep the lockfile in version control and have an update routine, so fixes do not depend on luck.
A worked example with invented numbers
The package and numbers below are invented to show how ranges play out.
Your manifest says that package "pdfmaker" has the range caret 3.1.0. Your lockfile, last refreshed in March, installed 3.1.4. In June, the authors publish 3.1.5 with a fix for a high severity flaw, and also 3.2.0 with new features.
- The caret range accepts 3.1.5 and 3.2.0, but not 4.0.0.
- Your lockfile still says 3.1.4, so your app still runs 3.1.4 and a scan flags the flaw.
- You refresh the lockfile. The package manager picks the newest version that fits, which is 3.2.0, not just the fixed 3.1.5. The flaw is gone, but you also got new features you did not test.
- If your range had been the tilde form of 3.1.0, you would have received 3.1.5, the smallest change that includes the fix.
Same fix, different amount of change. This is why people choose ranges on purpose. A narrower range means fewer surprises, and a wider one means fewer manual updates.
How do I check what I really have?
- Open the manifest and read the ranges for your most important packages.
- Open the lockfile and find the exact version installed for each.
- Compare that to the fixed versions in your scan report.
- If they differ, refresh the lockfile on a branch, run tests, and merge.
- Write down any exact pins you chose on purpose and why.
Try it yourself
The demo below shows an SCA dependency inventory in a sample workspace. Click through and look at how each component is listed with the exact version in use, which is the number that matters for security even when your manifest only shows a range.
Click through it at your own pace, or open it full screen.
Quick check
What is the difference between caret and tilde?
Tilde is narrower: it usually allows only patch updates. Caret is wider: for versions 1.0.0 and above it allows minor and patch updates, but not a new major version.
Why does a caret behave differently on version 0.x?
For 0.x versions, the caret allows only patch updates, and for 0.0.x it allows none. Many authors treat the 0.x number as the breaking change indicator.
Does my range or my lockfile decide what a scanner sees?
The lockfile. It records the exact versions installed, so scanners use it to see what you actually run. The range only says what is allowed.
How can Safeguard help with this?
Safeguard's SBOM and dependency scanning records the exact versions your project resolves to, not only the ranges you wrote, and checks them against known vulnerabilities. Policies and gates can then enforce rules on pull requests that change those versions. With autonomous remediation turned on, which is off by default with every category on Manual, fix pull requests are proposed for a person to review.
Run a free scan at gold.safeguard.sh/scan.
Sources
- npm Docs: package.json
- node-semver README
- Semantic Versioning 2.0.0
- npm Docs: package-lock.json
- npm Docs: npm audit
Last reviewed: 19 October 2026.