Skip to content

Conversation

@MaxBlack-dev
Copy link
Contributor

Description

This PR adds documentation explaining how npm install behaves with respect to package.json and package-lock.json, a common source of confusion for npm users.

Changes

  • Added a new section "How npm install uses package-lock.json" to the npm install documentation
  • Explained the two scenarios:
    • When package.json and package-lock.json are in sync: exact versions from lockfile are installed
    • When they conflict: package.json wins and package-lock.json is updated
  • Clarified that package.json is the source of truth for version ranges, while package-lock.json locks to specific versions
  • Noted the relationship to npm ci behavior

Context

The npm install documentation previously didn't explain how it handles the interaction between package.json and package-lock.json. Users were confused about when versions from the lockfile are used versus when they're updated. This PR incorporates the explanation from Kat Marchán that was referenced in the issue to provide clear guidance.

Closes #4866

@MaxBlack-dev MaxBlack-dev requested a review from a team as a code owner November 30, 2025 01:52

When you run `npm install` without arguments, npm verifies that `package.json` and `package-lock.json` are in sync:

* **If they match:** npm installs the exact versions specified in `package-lock.json`, ensuring reproducible builds across environments. This is similar to `npm ci` but also updates `package-lock.json` if needed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to contradict itself, it does not install the exact versions in package-lock if the package-lock needs to be updated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that! You're absolutely right - that was contradictory. I've updated the text to remove the confusing statement about updating package-lock.json from the "If they match" scenario, since that's already covered in the "If they don't match" case. The text now simply says npm "uses the versions specified in package-lock.json" without the contradictory language.

@MaxBlack-dev MaxBlack-dev force-pushed the docs/4866-package-lock-behavior branch from 11a4332 to 610564a Compare December 3, 2025 21:00

* **If they match:** npm uses the versions specified in `package-lock.json` to ensure reproducible builds across environments.

* **If they don't match:** If you've modified `package.json` so that the version ranges no longer match what's in `package-lock.json`, npm treats it as if you ran `npm install <package>@<new-version>` for the changed packages. It will update `package-lock.json` with the new resolved versions that satisfy the updated `package.json` ranges.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to explain what "in sync" means. There are two ways the file can be out of sync. If I have a package.json that says semver: ^7.0.0 and I have [email protected] in my tree, but then manually change package.json to have [email protected], when I next run npm install the only change to my lockfile will be the metadata in it that mirrors the package.json

[email protected] /Users/wraithgar/Development/scratch/lock
└── [email protected]

~/D/s/lock (main|✔) $ npm pkg get dependencies.semver
"^7.0.0"
~/D/s/lock (main|✔) $ npm pkg set dependencies.semver='^7.1.0'
~/D/s/lock (main|✚1) $ npm i

up to date in 1s
~/D/s/lock (main|✚2) $ git diff
diff --git a/package-lock.json b/package-lock.json
index ad48b26..1e047f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
-        "semver": "^7.7.3"
+        "semver": "^7.1.0"
       }
     },
     "node_modules/semver": {
diff --git a/package.json b/package.json
index 279765d..df1e3f3 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,6 @@
   "license": "ISC",
   "type": "commonjs",
   "dependencies": {
-    "semver": "^7.0.0"
+    "semver": "^7.1.0"
   }
 }

So the npm install <package>@<new-version> isn't wholly correct, as that would have also changed the package.json to have the new version as the new baseline for the version range.

~/D/s/lock (main|✚2) $ npm i semver@latest

up to date in 1s
~/D/s/lock (main|✚1) $ git diff 
diff --git a/package.json b/package.json
index 279765d..76daf15 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,6 @@
   "license": "ISC",
   "type": "commonjs",
   "dependencies": {
-    "semver": "^7.0.0"
+    "semver": "^7.7.3"
   }
 }

The other way of course is if you change package.json to have a range outside of what's currently set there. If I were to change semver from "^6" to "^7" then it would be closer to as if you ran npm install package@new-version but it still wouldn't change the entry in package.json.

the TLDR is we may want to avoid an "as if" and have a little more direct (yet succinct) explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right - I've removed the misleading "as if" analogy and provided a more direct explanation. The updated text now clarifies what "in sync" means (when lockfile versions satisfy package.json ranges) and explains the actual behavior: npm resolves new versions when ranges conflict and updates the lockfile accordingly. I also added the nuance you mentioned about metadata-only updates when changing ranges within the same major version (like ^7.0.0 to ^7.1.0). Thanks for the detailed examples!

* **If they don't match:** If you've modified `package.json` so that the version ranges no longer match what's in `package-lock.json`, npm treats it as if you ran `npm install <package>@<new-version>` for the changed packages. It will update `package-lock.json` with the new resolved versions that satisfy the updated `package.json` ranges.

In essence, `package-lock.json` locks your dependencies to specific versions, but `package.json` is the source of truth for acceptable version ranges. When they agree, the lockfile wins. When they conflict, `package.json` wins and the lockfile is updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to explain the way to NOT have npm change these files:

"If you want to install packages and ensure that these files match, and that npm will not change your package.json, consider running npm ci"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion! I've added a paragraph at the end of the section recommending npm ci for users who want to ensure strict synchronization without npm modifying package.json. This provides a clear path for those who need that behavior.

Adds documentation explaining how npm install handles the interaction
between package.json and package-lock.json:

- Clarifies when lockfile versions are used (when they satisfy package.json ranges)
- Explains when lockfile is updated (when ranges conflict)
- Notes that metadata-only updates occur for minor range changes
- Adds npm ci reference for strict sync without modifications

Closes npm#4866
@MaxBlack-dev MaxBlack-dev force-pushed the docs/4866-package-lock-behavior branch from 99188a7 to e2030e7 Compare December 12, 2025 18:58
@wraithgar
Copy link
Member

I re-read this after having not seen it for a few days and it read very very well. Thanks for working through this.

@wraithgar wraithgar merged commit 5552e46 into npm:latest Dec 17, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DOCS] Explain behavior of "npm install" w/r/t package versions in package-lock.json.

2 participants