|$ curl https://forge-ai.dev/api/markdown?path=docs/npm/semver
$cat docs/npm-—-semver-&-version-ranges.md
updated Recently·12 min read·published

npm — Semver & Version Ranges

npmBeginner
Introduction

Semantic Versioning (SemVer) is the versioning scheme used by npm and most of the JavaScript ecosystem. It provides a standard way to communicate the nature of changes between releases, enabling npm to safely resolve dependency ranges.

Understanding SemVer and npm version ranges is essential for managing dependencies, publishing packages, and avoiding unexpected breaking changes in your projects.

Semantic Versioning (major.minor.patch)

Every npm package version follows the MAJOR.MINOR.PATCH format. Each segment has a specific meaning that signals the type of change:

ComponentExampleMeaning
MAJOR3Breaking changes — may break existing code that depends on this package
MINOR2New features — backwards compatible, new functionality added
PATCH1Bug fixes — backwards compatible, bugs fixed, no new features
package.json
JSON
1// Example: package.json with version
2{
3 "name": "my-library",
4 "version": "3.2.1",
5 "description": "MAJOR 3 . MINOR 2 . PATCH 1"
6}
7
8// Version meaning:
9// 3.2.1 -> MAJOR=3, MINOR=2, PATCH=1
10// 0.x.x -> Initial development (anything may break)
11// 1.0.0 -> First stable release

info

Major version zero (0.x.x) is for initial development. According to SemVer spec, anything may change at any time. Once you hit 1.0.0, you are making a commitment to stability and following SemVer rules.
The caret ^ Range

The caret (^) is the default range specifier in npm. It allows changes at the leftmost non-zero component. This is the most common range operator.

package.json
JSON
1// ^ allows MINOR and PATCH updates
2// but not MAJOR updates
3
4"^1.2.3" // >=1.2.3 and <2.0.0
5"^0.2.3" // >=0.2.3 and <0.3.0 (leftmost non-zero is MINOR)
6"^0.0.3" // >=0.0.3 and <0.0.4 (leftmost non-zero is PATCH)
7"^1" // >=1.0.0 and <2.0.0
8"^1.2" // >=1.2.0 and <2.0.0
9
10// Examples of what gets installed:
11"^1.0.0" can install 1.0.0, 1.0.1, 1.1.0, 1.5.3
12 but NOT 2.0.0
13
14"^0.1.0" can install 0.1.0, 0.1.1, 0.1.5
15 but NOT 0.2.0 or 1.0.0

best practice

The caret range is npm's default because it strikes the right balance between getting updates and avoiding breaking changes. For most dependencies, ^1.2.3 is the recommended range.
The tilde ~ Range

The tilde (~) is more restrictive than caret. It allows only PATCH-level changes by default, or MINOR changes when the minor version is specified.

package.json
JSON
1// ~ allows only the most specific level to change
2
3"~1.2.3" // >=1.2.3 and <1.3.0 (only PATCH)
4"~1.2" // >=1.2.0 and <1.3.0 (only PATCH)
5"~1" // >=1.0.0 and <2.0.0 (same as ^1)
6"~0.2.3" // >=0.2.3 and <0.3.0
7
8// Compared with caret:
9// ^1.2.3 -> 1.2.3 to 1.999.999 (MINOR + PATCH)
10// ~1.2.3 -> 1.2.3 to 1.2.999 (just PATCH)
11
12// Use tilde when you want maximum stability:
13"~4.17.21" // lodash: only patch updates within 4.17.x
14"~3.0.0" // bootstrap: only critical fixes

info

Use ~ for packages where you want to minimize unexpected changes. This is common for large frameworks (React, Angular, Bootstrap) where minor version bumps can introduce significant changes even if they are technically backwards compatible.
Exact Version Pinning

Pinning to an exact version ensures everyone installs the identical dependency. This gives maximum reproducibility but requires manual updates for bug fixes and security patches.

package.json
JSON
1// Exact version — no range specifier
2"lodash": "4.17.21"
3"react": "18.3.1"
4"express": "4.21.0"
5
6// Save with exact version
7npm install lodash --save-exact
8# or set npm config:
9npm config set save-exact true
10
11// Advantages:
12// - Fully reproducible builds
13// - No surprises from dependency updates
14// - Recommended for production applications
15
16// Disadvantages:
17// - You miss bug fixes and security patches
18// - Requires manual effort to stay updated
19
20// Best practice: pin exact versions in applications,
21// use ranges in libraries

best practice

Applications should consider using exact versions for maximum stability. Libraries should use version ranges (^) to allow consumers flexibility. Use tools like Dependabot or Renovate to automate updates for pinned dependencies.
Pre-release Tags

Pre-release tags denote versions that are not yet stable. They are appended to the version with a hyphen and have lower precedence than the normal release.

package.json
JSON
1// Pre-release tags follow: MAJOR.MINOR.PATCH-TAG
2"1.0.0-alpha.1" // Alpha release
3"1.0.0-beta.2" // Beta release
4"1.0.0-rc.1" // Release candidate
5"2.0.0-dev.20240304" // Development build with date
6
7// Precedence (lowest to highest):
8// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta
9// < 1.0.0-beta.2 < 1.0.0-rc.1 < 1.0.0
10
11// Range behavior with pre-release:
12// ^1.0.0-alpha does NOT match 1.0.0-alpha.1
13// Use >=1.0.0-alpha <1.0.0 for pre-release ranges
14
15// npm install pre-release versions:
16npm install my-package@next # 'next' dist-tag
17npm install my-package@1.0.0-beta # specific pre-release

warning

Pre-release versions are not automatically matched by caret or tilde ranges. If you specify ^1.0.0 and the latest version is 1.0.0-beta, npm will not install the pre-release. You must specify the pre-release tag explicitly.
npm version Command

The npm version command automatically bumps the version in package.json, creates a Git tag, and optionally commits the change. It understands SemVer and increments the correct segment.

terminal
Bash
1# Bump each segment:
2npm version patch # 1.0.0 -> 1.0.1
3npm version minor # 1.0.1 -> 1.1.0
4npm version major # 1.1.0 -> 2.0.0
5
6# Pre-release bumps:
7npm version premajor # 1.0.0 -> 2.0.0-0
8npm version preminor # 1.0.0 -> 1.1.0-0
9npm version prepatch # 1.0.0 -> 1.0.1-0
10npm version prerelease # 1.0.1-0 -> 1.0.1-1
11
12# Custom pre-release identifier:
13npm version premajor --preid=alpha # 1.0.0 -> 2.0.0-alpha.0
14
15# Skip Git tag creation:
16npm version patch --no-git-tag-version
17
18# Skip commit:
19npm version patch --no-commit-hooks
20
21# From a specific version string:
22npm version "2.0.0-beta" # set exact version
23
24# View current version:
25npm version
26# { 'my-package': '1.2.3', npm: '10.5.0', ... }
Version Ranges in package.json

Beyond caret and tilde, npm supports a full set of range operators for complex dependency resolution:

package.json
JSON
1// Comparison operators:
2">=1.2.3" // Greater than or equal to
3"<=1.2.3" // Less than or equal to
4">1.2.3" // Strictly greater than
5"<1.2.3" // Strictly less than
6
7// AND conditions (space separated):
8">=1.2.0 <2.0.0" // Equivalent to ^1.2.0
9
10// OR conditions (|| operator):
11"1.2.3 || 1.3.0" // Either exact version
12"^1.0.0 || ^2.0.0" // Either 1.x or 2.x
13
14// Hyphen ranges:
15"1.2.3 - 2.3.4" // >=1.2.3 <=2.3.4
16
17// Wildcard/partial versions:
18"1.2.x" // >=1.2.0 <1.3.0
19"1.*" // >=1.0.0 <2.0.0
20"*" // any version
21
22// X-ranges (same as wildcard):
23"1.2.X" // >=1.2.0 <1.3.0
24
25// URL dependencies:
26"git+ssh://git@github.com:user/repo.git#v1.0.0"
27"github:user/repo"
28"file:../local-package"
29
30// Combined example:
31{
32 "dependencies": {
33 "express": "^4.21.0",
34 "lodash": "4.17.21",
35 "debug": ">=2.0.0 <4.0.0",
36 "my-lib": "file:./packages/my-lib"
37 }
38}
Dist Tags & publish tags

Distribution tags (dist-tags) are human-readable labels that point to specific versions. They allow users to install packages by tag name instead of version number.

terminal
Bash
1# Default dist-tags:
2# latest — points to the most recent stable release
3# next — points to the next upcoming release
4
5# View dist-tags for a package:
6npm dist-tag ls my-package
7# latest: 2.0.0
8# next: 2.1.0-beta.1
9# experimental: 3.0.0-alpha.1
10
11# Add a dist-tag:
12npm dist-tag add my-package@1.0.0 stable
13
14# Remove a dist-tag:
15npm dist-tag rm my-package experimental
16
17# Install using a dist-tag:
18npm install my-package@next
19npm install my-package@latest
20
21# Publish with a dist-tag:
22npm publish --tag next
23npm publish --tag experimental
24
25# The 'latest' tag is used by default when:
26# - npm install my-package (no version specified)
27# - npm install my-package@latest
🔥

pro tip

Use npm publish --tag next for pre-release versions. This allows users to opt into testing without accidentally installing an unstable version via npm install (which defaults to the latest tag).
Best Practices

Use ^ for Library Dependencies

Libraries should use caret ranges to give consumers flexibility. Pinning exact versions in a library forces all downstream projects to use that exact version, causing duplication and bloated node_modules.

Pin Exact Versions in Applications

For production applications, pin exact versions to ensure reproducible builds. Use tools like Renovate or Dependabot to automate security updates.

Use --save-exact for Critical Dependencies

Run npm config set save-exact true if your team policy requires exact versions. Or use npm install --save-exact for individual packages.

Understand Lock Files

Even with ranges in package.json, the lock file (package-lock.json) records the exact resolved versions. This ensures reproducible installs across environments regardless of the range specifiers.

$Blueprint — Engineering Documentation·Section ID: NPM-SEMVER·Revision: 1.0