Key Takeaways
npm updaterefreshes eligible dependencies within their declared version ranges; it doesn't necessarily install the newest published release.- Declared ranges normally stay unchanged, while the resolved dependency tree and
package-lock.jsoncan change. - Development dependencies aren't excluded by default. Inclusion and omission settings control which dependency types are installed on disk.
- Use
npm install package@versionto request a different version, andnpm cito reproduce an existing lockfile.
npm update updates local dependencies within the semver ranges in package.json and normally records the resulting dependency tree in package-lock.json. It preserves your declared ranges by default: updating an installed version and changing the versions your project requests are separate operations. The npm update reference documents that distinction.
How npm update respects semver ranges
Semantic versioning, or semver, expresses versions as major, minor, and patch numbers. Your dependency range determines which versions npm may select; the installed version is one particular resolution of that range.
Consider this illustrative package.json fragment. dep1 and the available releases below are hypothetical:
{
"dependencies": {
"dep1": "~1.2.3"
}
}The range ~1.2.3 accepts stable versions starting at 1.2.3 but below 1.3.0. If the highest eligible release is 1.2.9 and the registry's latest release is 1.3.0, npm can update to 1.2.9, not 1.3.0.
By comparison, ^1.2.3 permits stable releases below 2.0.0, while an exact declaration of 1.2.3 permits no other version. Caret ranges behave more narrowly before 1.0.0; for example, ^0.2.3 excludes 0.3.0. See npm's semver range reference for the precise rules.
Updating all local packages or one package
Run these commands from your project's root directory. Without package arguments, npm considers all eligible local packages; supplying a name targets that package:
# Update eligible local packages.
npm update
# Target Express within its existing range.
npm update expressA targeted update can also change related transitive dependencies—the packages your dependencies depend on. Don't assume that updating one direct dependency produces a one-package lockfile diff. npm can also install missing packages, subject to the dependency tree and omission settings.
Updating devDependencies
npm update normally includes development dependencies. When NODE_ENV=production, npm defaults to omitting dev dependencies from the installed tree; configuration can also request that omission.
# Explicitly include development dependencies.
npm update --include=dev
# Omit development dependencies from the on-disk installation.
npm update --omit=dev--include=dev wins if dev dependencies are also listed for omission. It doesn't mean “update only dev dependencies,” and omitted dependencies can still be resolved and recorded in the lockfile. These are installation controls, documented in npm's configuration reference.
--save-dev has a different job: with npm install, it saves a package under devDependencies. It isn't a replacement for --include=dev.
Inspect available updates with npm outdated
Before changing dependencies, use npm outdated to distinguish installed versions from available updates:
npm outdated
npm outdated express| Column | Meaning |
|---|---|
| Current | The installed version. |
| Wanted | The highest version satisfying the package.json range. |
| Latest | The version carrying the registry's latest dist-tag. |
A dist-tag is a named pointer to a release. Latest isn't necessarily the highest version number or the most recently published version. If Current equals Wanted but Latest differs, your existing range may already be fully updated.
npm update vs npm install: changing the requested version
Choose the command according to your intent:
Keep existing ranges and refresh resolutions?
-> npm update
Request a particular version or a different range?
-> npm install package@version
Reproduce the committed dependency tree?
-> npm ciTo request a particular release, use npm install. The following are command templates: replace <version> with a published version before running them.
# Request a version; normal save settings determine the saved range.
npm install "express@<version>"
# Save an exact version rather than a range.
npm install --save-exact "express@<version>"
# Save a requested version as a development dependency.
npm install --save-dev "eslint@<version>"Saving is the default for npm install, so an extra --save isn't normally necessary. Unless configured otherwise, npm prefixes saved versions with ^; use --save-exact when you want an exact declaration.
You can also request a dist-tag, such as npm install express@latest, or supply a quoted version range. Either is an intentional change to what your project requests, not simply a refresh within its previous range. See the npm install reference and our explanation of how npm install works.
There is an exception to update's normal manifest behavior: npm update --save, or an explicitly configured save=true, can rewrite dependency declarations. Leave that behavior off when preserving the existing ranges is your goal.
npm update and package-lock.json
The three files and directories serve different purposes:
package.jsondeclares acceptable dependency versions.package-lock.jsonrecords the resolved tree, including exact package versions and package metadata.node_modulescontains the installed packages.
npm update recalculates eligible resolutions instead of treating existing locked versions as permanent. npm writes the resulting lockfile under normal lockfile settings. Manually editing package.json or files inside node_modules doesn't automatically synchronize the lockfile.
After manually changing a declared range, run npm install to reconcile it with the lockfile. Without arguments, install normally retains locked versions that still satisfy your manifest rather than refreshing every dependency.
For an existing Express project tracked in Git, inspect, update, and review:
npm outdated express
npm update express
git diff -- package.json package-lock.jsonUse git diff to check both files. A compatible update should normally leave the declared range alone while changing resolved versions and any affected metadata.
In the hypothetical dep1 example, the relevant changes would look like this schematic excerpt—not a complete lockfile:
package.json:
"dep1": "~1.2.3" unchanged
package-lock.json, packages["node_modules/dep1"]:
- "version": "1.2.3"
+ "version": "1.2.9"
Other affected metadata and dependencies are omitted here.Modern npm generates lockfileVersion: 3, rather than the original lockfile format. Let npm maintain the structure instead of editing version fields by hand; the lockfile reference explains its contents.
Use npm ci to reproduce, not update
npm ci installs from an existing lockfile without rewriting either manifest or lockfile. It removes an existing node_modules first and fails when the manifest and lockfile disagree instead of repairing them. Commit the reviewed lockfile so CI can reproduce the selected tree.
Updating global npm packages
Global mode operates on npm's configured global installation prefix, not your project's dependencies. Use -g or its long spelling, --global:
# Inspect global updates.
npm outdated -g
# Update only Express, if it is installed globally.
npm update -g express
# Update all eligible global packages.
npm update -gUnlike local dependencies, global packages have no project-level package.json range to preserve: npm treats latest as their wanted version. Global updates can therefore cross major versions; a package installed ahead of latest can even be downgraded.
Don't bulk-update shared global tools immediately before a release. Prefer project-local development tools when the team needs a reviewed, locked toolchain, and coordinate the runtime using the guidance on which Node.js version to use.
npm update not working? Diagnose the actual constraint
Check the declared range and registry metadata
First determine whether a newer allowed release exists. Use npm pkg to inspect your declarations and npm view to inspect published metadata:
npm outdated express
npm pkg get dependencies.express devDependencies.express
npm view express dist-tags --jsonAn exact pin or an already-satisfied range can explain an unchanged package. If you need a release outside the range, change the request deliberately with npm install; repeating update won't widen it.
Check configuration and read the error output
Inspect effective settings with npm config, then rerun a failed update with more logging:
npm config get registry omit package-lock
npm update express --loglevel=verboseCheck the registry, authentication, network access, and any scoped registry settings in .npmrc configuration files. Omission settings can explain missing dev packages; disabled lockfile writing can explain a missing lockfile diff. Don't share logs or configuration containing credentials.
Resolve peer-dependency conflicts instead of hiding them
A peer dependency expresses a package's compatibility requirement for another package in the consuming tree. When npm reports a conflict, compare the packages and requested ranges in that error and choose compatible versions. Don't use --legacy-peer-deps as an unexplained fix: it ignores peer-dependency requirements while constructing the tree.
Trace transitive dependencies to their parents
Use npm explain to identify why a package is installed:
# In a project with Express installed:
npm explain expressNaming a transitive package doesn't give npm permission to violate its parent's requirements. Prefer updating the parent dependency; when necessary, evaluate a root-level overrides declaration and test that combination explicitly. Installing a transitive package directly isn't a reliable way to replace every nested copy.
Production Checklist
- Start from a clean working tree so unrelated changes don't hide dependency changes.
- Review the manifest and lockfile diff, release notes, and newly introduced dependencies.
- Run your project's tests, lint checks, and production build before committing.
- Validate a clean installation with
npm ci, using the same relevant installation settings as CI. - For Express updates, review application behavior alongside these Express security practices.
FAQ
Does npm update change package.json?
By default, npm update preserves the declared version ranges in package.json. Passing --save or explicitly configuring save=true can change those declarations.
Does npm update include devDependencies?
Normally, yes. Use --include=dev to explicitly include development dependencies when omission settings would exclude them from disk; --save-dev controls where npm install saves a dependency instead.
How do I update one package with npm?
Run npm update express to target Express within its existing range. Use npm install express@<version>, replacing the placeholder, when you intend to request a particular release.
Why does npm update not install the latest version?
The latest release may fall outside your declared range, or dependency-tree constraints and npm configuration may limit selection. Compare Current, Wanted, and Latest with npm outdated, then inspect the range and any errors.
Does npm update modify package-lock.json?
Yes, under normal lockfile settings, npm update records changed dependency resolutions in package-lock.json. Manual file edits don't synchronize it automatically, and npm ci reproduces the lockfile rather than updating it.
Further Reading
- npm update reference — selection rules, manifest behavior, and global updates.
- npm outdated reference — interpreting wanted versions and dist-tags.
- npm install reference — explicit versions, ranges, and save options.
- npm ci reference — clean, lockfile-driven installations.
- package-lock.json reference — resolved trees and lockfile formats.
- npm's semver implementation — tilde, caret, exact, and prerelease matching.
- npm configuration reference — inclusion, omission, saving, and peer-dependency controls.
- package.json reference — dependency declarations and overrides.
