They pin different things. save-exact makes npm write exact versions for the packages you add to package.json. The lockfile records the resolved versions for the whole tree, including transitive packages. For an application, the committed lockfile with npm ci does the real pinning, and save-exact is an extra.
What each setting does
The npm config reference defines save-exact as a boolean, default false: saved dependencies get an exact version rather than npm's default semver range operator. It applies when you run an install that saves a dependency.
The npm ci documentation describes the lockfile side. The command needs a package-lock.json or npm-shrinkwrap.json, errors if the lock and package.json disagree instead of updating the lock, deletes an existing node_modules, and never writes to package.json or the lockfile. The docs call such installs essentially frozen.
Side by side
| Question | save-exact | Committed lockfile with npm ci |
|---|---|---|
| Pins direct dependencies | Yes, when saving | Yes |
| Pins transitive dependencies | No | Yes |
| Works for packages already in package.json | No, only new saves | Yes |
| Fails the build if versions drift | No | Yes, on a mismatch |
| Affects consumers of a library | Yes, stricter ranges | No, package-lock.json is not in the published tarball |
The last row matters for libraries. The npm package.json docs list lockfiles such as package-lock.json among the files always left out of the tarball, so a library's lockfile does not control what its users install.
Why save-exact alone falls short
Most supply chain incidents arrive through transitive packages. An exact version on your direct dependency still allows that dependency's own ranges to resolve to a newer release on a fresh install. Without a lockfile, two installs on different days can differ.
What to do
- Commit the lockfile and use
npm ciin CI and in image builds. - Optionally set
save-exact=truein.npmrc, so new additions are visible as exact numbers in review. This is a style choice with a small benefit. - Review lockfile changes in pull requests. See what to check in a lockfile diff.
- Update on a schedule, not by accident. Pair updates with a release delay such as dependency cooldown periods.
- Do not set
package-lock=falsefor an application. The config page says that setting makes npm ignore existing lockfiles and stop writing them.
What neither one proves
Pinning controls what you get, not whether it is safe. A pinned malicious version stays malicious. You still need scanning of the pinned tree.
Try it yourself
Open the dependency inventory demo and see how a scan lists every package in a tree, including transitive ones that save-exact never touches.
Click through it at your own pace, or open it full screen.
Safeguard builds SBOMs and scans the full dependency tree from your lockfile. Start with a free scan.
Quick check
Does save-exact change packages already in package.json?
No. It affects how new saves are written. Edit existing ranges by hand if you want them exact.
Does npm ci ever update the lockfile?
No. The docs say it never modifies package.json or the lockfile.
Sources
Last reviewed: 19 October 2026.