Semantic versioning is a convention for writing version numbers so they communicate what kind of change happened, before anyone reads a changelog. A version reads as three numbers — major, minor, and patch — and each position means something specific: patch means a safe bug fix, minor means a safe new addition, major means something might break. Once you know how to read that promise, a dependency update stops being a guess and becomes a decision you can actually make with information.
What changed in 2026
- Version ranges got more scrutiny from supply chain security tooling, with package managers surfacing exactly what a range would allow before you install, not just after something breaks.
- Registries locked down published versions further. You can deprecate a version, but not delete or silently replace it, which makes the version number a more permanent record than it used to feel like.
- Automated release tooling made strict adherence to the convention more common, since tools that calculate version bumps from commit messages only work if the convention is actually followed consistently.
- Pre-1.0 packages became more visible as a risk signal in dependency audit tools, which now commonly flag
0.x dependencies distinctly from stable ones.
Reading a version number
2.14.3
| | |
| | +- PATCH: bug fixes only, safe to take automatically
| +---- MINOR: new features, backward compatible, safe to take
+------ MAJOR: breaking changes, read the changelog before upgrading
That is the whole contract. A jump from 2.14.3 to 2.14.4 should never require you to change your own code. A jump to 2.15.0 should be safe too, just with something new available. A jump to 3.0.0 is the one that earns a careful look before you upgrade.
How version ranges actually resolve
Your manifest rarely pins an exact version; it specifies a range, and your package manager picks the highest version matching that range at install time, then records the exact result in a lockfile.
| Range syntax |
Example |
What it allows |
| Caret (^) |
^2.14.0 |
Any 2.x.x version, 2.14.0 or higher — minor and patch updates |
| Tilde (~) |
~2.14.0 |
Any 2.14.x version — patch updates only |
| Exact |
2.14.0 |
Only that exact version, nothing else |
| Greater than or equal |
>=2.14.0 |
That version or any newer one, including future majors |
| Range union |
2.14.0 or 3.0.0 |
Either of two specific versions |
The caret is npm's default when you install a package, which is why most package.json files are full of caret ranges: it is the version manager's way of trusting the semantic versioning promise, taking new features and fixes automatically but never a breaking change without an explicit decision.
The version zero exception
Semantic versioning carves out one deliberate exception: anything below 1.0.0 has no stability guarantee at all. A 0.4.0 to 0.5.0 bump is allowed to break you, by convention, because the maintainer is signaling the API is still taking shape. If you depend on a 0.x package, read the changelog on every minor bump, not just major ones, since the normal safety rules do not apply yet.
Why the convention sometimes fails you
Semantic versioning is a promise a maintainer chooses to keep, not something enforced by the registry. A patch release that accidentally breaks something happens: a dependency update, an edge case nobody tested, a change that looked safe but was not. This is exactly why a lockfile matters: it freezes what actually got installed, so an unexpected bad patch release does not silently reach your project the next time someone runs an install.
FAQ
Do I need to understand semantic versioning if I never publish a package?
Yes. Every project that installs dependencies is a consumer of other people's version numbers. Understanding what a range like ^2.14.0 actually permits is what lets you judge how much risk an update carries.
What happens if I want to block updates entirely?
Pin an exact version with no range operator. This trades automatic safe updates for full control, which is common in production environments where every dependency change should be a deliberate, reviewed decision.
Is a major version bump always going to break my code?
Not necessarily. The maintainer might be bumping major out of caution, or the breaking change might not touch the part of the package you use. It does mean you should read the changelog before upgrading, which a minor or patch bump does not require.
How does this relate to choosing between npm, yarn, and pnpm?
All three read and resolve version ranges the same way; the syntax and the promise behind it are not tool-specific. See npm vs yarn vs pnpm for how the tools differ on everything else.
Where to go next