Semver and dependency selection

How npm version ranges affect compatibility

Read caret and tilde ranges, understand 0.x and prerelease behavior, and separate a declared range from an installed version.

Based on official documentationUpdated

The core answer

A dependency range describes versions that are eligible for selection; it is not the exact version installed. Caret and tilde ranges expand differently, especially for 0.x releases. Inspect the lockfile and installed version, then check that exact release's engines and exports before making a compatibility decision.

Caret and tilde do not mean the same update window

node-semver, the version-range parser used by npm, defines caret ranges around the leftmost non-zero version component. A tilde range with a minor version allows patch updates within that minor. For normal stable versions, these examples show where the next boundary sits.

Stable version examples for npm ranges
RangeIncluded exampleExcluded example
^1.2.31.9.02.0.0
~1.2.31.2.91.3.0
^0.2.30.2.40.3.0
^0.0.30.0.30.0.4

Prereleases need an explicit decision

Prerelease versions are generally excluded from ordinary range matching. A prerelease comparator can opt in to prereleases with the same major, minor, and patch tuple; this does not automatically opt in to every later beta or release candidate.

A matching range reflects version-selection rules, not a guarantee that an update cannot break your application. Read the release notes and migration guidance, particularly when the package has a 0.x version or the change affects module format or Node requirements.

Illustrative package.json · a range, not an installed version
{
  "dependencies": {
    "example-package": "^0.2.3"
  }
}

The range admits stable 0.2.x versions from 0.2.3 and excludes 0.3.0. Inspect the lockfile to learn which release was resolved. example-package is a placeholder, not a package recommendation.

Keep selection and reproduction connected

package.json expresses your requested constraints. package-lock.json records a resolved dependency tree. npm ci requires a lockfile consistent with package.json and installs from it, helping reproduce the dependency selection in CI.

An exact top-level pin alone does not pin every transitive dependency or runtime. Keep the lockfile, Node version, package-manager version, and relevant platform constraints in the review when evaluating an upgrade.

What to check next

  1. 1

    Separate the requested range from the installed release

    Read package.json, the lockfile, and npm ls package-name. If an error names a transitive dependency, locate that exact release rather than checking only the direct dependency.

  2. 2

    Check the proposed release's declarations

    Compare its engines.node and exports with your runtime and importing style. Evaluate prereleases intentionally and use the changelog to understand changes that metadata cannot describe.

  3. 3

    Review and reproduce the dependency update

    Inspect the resulting lockfile changes and run your application checks. Use npm ci in CI with a committed, consistent lockfile to reproduce the reviewed dependency tree.

What a declaration cannot establish

  • A semver match does not guarantee API compatibility, security, or correctness for your application.
  • Dist-tags such as latest can move; they are not fixed semantic versions.
  • The compatibility checker evaluates an exact selected release and does not simulate package-manager dependency resolution.

Official references

From explanation to inspection

Check an exact package release.

Compare a package's declared Node requirements and root entry point with your environment, then verify it in your application.

Prefilled example: chalk 5.6.2 · Node 20.0.0 · CommonJS. Run the checker to retrieve the declaration; this guide does not claim an observed result.

Inspect the example
All guides