CLI tool
npm/node-semver avatar
npm/node-semver

npm/node-semver: the parser behind npm's version ranges

The semver parser for node (the one npm uses)

5,465 stars606 forksJavaScriptISC

At a glance

What is it?
node-semver is the JavaScript implementation of the SemVer 2.0.0 specification that npm uses to resolve dependency ranges. This review covers its module and CLI surfaces, the prerelease matching rule that trips people up, and when to reach for something else.
Who is it for?
Adopt node-semver if you are writing JavaScript tooling that has to compare versions or evaluate ranges the same way npm does, because matching npm's behaviour exactly is the whole point of the package. Do not adopt it as a general-purpose version utility for other languages, and do not expect the README to explain prerelease matching in full: read the Prerelease Tags section before you ship a range evaluator.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What node-semver does that a string comparison cannot

Version strings sort badly as text. "10.0.0" comes before "9.0.0" lexically, and "1.2.3-alpha.3" is not comparable to "1.2.3" without knowing that a prerelease tag sorts below the release. node-semver parses a version into its major, minor, patch, prerelease and build parts, then compares those parts by the rules in the SemVer 2.0.0 specification. The package description in package.json states it plainly: it is the semantic version parser used by npm.

That last clause is the reason to pick this library over any other JavaScript SemVer implementation. If your tool reads package.json files, resolves dependency trees, or decides which published version to install, you want the same answer npm would give. A range like 1.x || >=2.5.0 || 5.0.0 - 7.2.3 is not a regular expression problem; it is a set of comparators joined by whitespace and ||, and the README documents that a version matches only when every comparator in at least one of the ||-separated sets is satisfied.

The audience is narrow but deep: build tooling authors, registry clients, release automation, monorepo scripts, and anyone writing a linter or migration tool that has to reason about what a dependency range actually permits.

How the parser, comparators and ranges fit together

The repository layout mirrors the mental model. classes/ holds SemVer, Comparator and Range. functions/ holds the per-version operations (parse, valid, clean, inc, diff, major, minor, patch, prerelease, compare, rcompare, compare-loose, compare-build, sort, rsort, truncate). ranges/ holds the operations that involve a range rather than a single version: satisfies, max-satisfying, min-satisfying, to-comparators, min-version, valid, outside, gtr, ltr, intersects, simplify and subset. There is also a range.bnf grammar file in the published package, which is the formal statement of the range syntax the parser implements.

index.js loads everything at once. preload.js and the per-function paths exist so you can require only what you need, which the README demonstrates by listing every entry point individually. That matters for bundlers and for cold-start time in CLIs.

The data flow is: a version string or range string goes in, the parser either produces a structured object or null, and the comparison functions operate on those structures. semver.valid returns the normalised string or null, so it doubles as a validity check and a normaliser. semver.clean strips surrounding whitespace and a leading = or v, which the README notes is kept for compatibility with v1.0.0 of the specification and should not be used anymore. semver.coerce goes further and extracts a version out of a string that is not one, which is why semver.coerce('v2') normalises to '2.0.0'.

Installing node-semver and running a first range check

The README gives one install command and no build step. The package has no runtime dependencies, and package.json sets engines to node >=10, so any currently supported Node release works.

bash
npm install semver

After that, requiring the whole API is the shortest path. The README's own example shows valid, clean, satisfies, gt, lt, minVersion and coerce side by side.

js
const semver = require('semver')

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean('  =v1.2.3   ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true
semver.minVersion('>=1.0.0') // '1.0.0'

The return values are the point. valid gives you the string back or null, so a truthiness check tells you whether the input was parseable. minVersion gives you the lowest version a range accepts, which is what you want when you are deciding whether a floor is too high.

There is also a CLI, installed as bin/semver.js and exposed as the semver command. The help text describes its usage as semver [options] <version> [<version> [...]], printing valid versions sorted by SemVer precedence. Ranges are selected with -r, and the process exits successfully if any valid version satisfies all supplied ranges, printing the satisfying versions. The README's own example of a range is >=1.2.7 <1.3.0, which it states matches 1.2.7, 1.2.8 and 1.2.99 but not 1.2.6, 1.3.0 or 1.1.0. If nothing satisfies the range, the exit code is a failure, which makes the CLI usable as a shell condition.

The prerelease rule is where most integrations go wrong

A version with a prerelease tag does not satisfy a comparator set just because it sorts higher. The README is explicit: if a version has a prerelease tag, it is only allowed to satisfy comparator sets if at least one comparator with the same [major, minor, patch] tuple also has a prerelease tag. The documented example is >1.2.3-alpha.3, which matches 1.2.3-alpha.7 but is not satisfied by 3.4.5-alpha.9, even though 3.4.5-alpha.9 is greater than 1.2.3-alpha.3 under the SemVer sort rules. The range only accepts prerelease tags on the 1.2.3 version. Plain 3.4.5 does satisfy it, because it carries no prerelease flag.

This is deliberate and it is the behaviour npm relies on, but it surprises people who expect range matching to be pure ordering. If you build a release dashboard that lists candidate versions for a range, you will get an empty list whenever your only newer versions are prereleases. The CLI offers an escape hatch: the -p / --include-prerelease flag always includes prerelease versions in range matching. The README does not describe what else that flag changes, so treat it as a coarse switch rather than a precise one.

Two other behaviours are worth flagging before you rely on them. The -l / --loose option interprets versions and ranges loosely, and -c / --coerce does not imply --loose, which means coercing a malformed string and parsing it loosely are separate decisions. And --rtl / --ltr control the direction coercion scans a string, defaulting to left to right. The README documents the flags but gives no worked examples for any of them.

Where node-semver is the wrong dependency

The package is JavaScript only. There is no Python binding, no Go port, no cross-language protocol. If your toolchain is not Node, you are reimplementing the spec or choosing a different library, and the README gives you nothing to bridge that gap.

More subtly, node-semver implements npm's interpretation of ranges, which is a superset of the specification. Caret and tilde ranges, hyphen ranges, x-ranges and the || union are npm conventions, not SemVer 2.0.0. The specification covers version precedence; it says nothing about 1.x or ^1.2.3. If you are validating version strings against the spec itself, the range functions are irrelevant and arguably misleading, because a range that parses here may be meaningless outside the npm ecosystem.

The loose mode is a second sharp edge. Accepting malformed versions is exactly what you want when scraping a changelog, and exactly what you do not want at a publish gate, where a typo should fail loudly. The README does not document which malformations loose mode tolerates, so there is no way to reason about its acceptance set from the documentation alone. If you need a strict gate, do not enable it.

Finally, the README does not document rollback or downgrade behaviour for the package itself, and the repository carries no migration guide between major versions. Upgrading across a major boundary means reading CHANGELOG.md.

Alternatives and the difference in approach

The most direct alternative is to write the comparison yourself. For a single check like "is this version at least 1.2.3", splitting on dots and comparing three integers is a dozen lines and no dependency. That approach breaks the moment you meet a prerelease tag, a build identifier, or a range, because the SemVer precedence rules for prerelease fields are not numeric comparison. node-semver exists to absorb that complexity, and its range.bnf file is the evidence that the grammar is non-trivial.

A second alternative is a version manager or package manager that bundles its own resolver. Those tools evaluate ranges as part of a larger job, so they give you no API to ask a single question like "what is the minimum version this range accepts". If you need that answer inside your own program, you need a library, not a package manager.

A third is a SemVer library in another language. If your service is Python and the rest of your stack is JavaScript, keeping the version logic in the Python service avoids a cross-language call, at the cost of two implementations that can disagree at the edges. The README offers no compatibility statement for such a split, so any divergence is yours to find and test.

Maintenance, licence and the cost of upgrading

The repository is not archived, and its last push was on 2026-09-10. Recent releases are v7.8.3 on 2026-06-08, v7.8.4 on 2026-06-09 and v7.8.5 on 2026-06-19. The project is maintained by GitHub Inc. under the npm organisation, and package.json notes that the file is partially managed by @npmcli/template-oss, with edits possibly overwritten. That is a real constraint: if you fork and edit package.json, expect the template tooling to fight you.

Licensing is ISC, a permissive licence that imposes few conditions beyond retaining the copyright notice and permission notice. That is a summary of the identifier in package.json and the LICENSE file, not legal advice; check the LICENSE text against your own policy before you redistribute.

The upgrade cost is low in the ordinary case. The package has no runtime dependencies, so it cannot drag in a transitive tree, and engines is set to node >=10, which is below every Node release still receiving support. The friction is concentrated in major-version jumps, where the public API surface is large: index.js, preload.js, classes/, functions/, ranges/ and range.bnf are all published, and the template-oss distPaths list confirms they are the intended distribution. Anything you require by deep path, such as semver/functions/coerce or semver/ranges/subset, is part of that surface and can move. Requiring the index is the lower-risk choice if you do not care about bundle size.

Editorial conclusion

Adopt node-semver if you are writing JavaScript tooling that has to compare versions or evaluate ranges the same way npm does, because matching npm's behaviour exactly is the whole point of the package. Do not adopt it as a general-purpose version utility for other languages, and do not expect the README to explain prerelease matching in full: read the Prerelease Tags section before you ship a range evaluator. Verify first that the prerelease rule matches your release process by running semver -r on a version list that includes a prerelease tag.

Frequently asked questions

What is node-semver used for?

It parses and compares semantic version strings and evaluates version ranges, following the SemVer 2.0.0 specification plus npm's range conventions. package.json describes it as the semantic version parser used by npm.

What is the difference between Git and node-semver?

They address different problems. node-semver parses and compares version strings and ranges; Git is a version control system, and nothing in the README or repository connects the two.

How do I install node-semver?

Run npm install semver. The README gives that as the only install step, and package.json lists no runtime dependencies.

What is node semver?

It is the JavaScript implementation of the SemVer 2.0.0 specification that npm uses, published as the semver package with a module API and a command-line utility.

Official sources

  1. License: ISC
  2. npm/node-semver on GitHub
  3. Project website
  4. README
  5. Releases
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/npm-node-semver.svg)](https://hysenlabs.com/projects/npm-node-semver)
Community notes

Community notes