TechToolsCenter

Can't find the tool you're looking for?

Request it and vote on what we build next — it takes 20 seconds.

Request a Tool
TechToolsCenter

All Your Essential Tools. One Center. Free, fast, privacy-first online tools that run entirely in your browser.

Built for speed. Designed for privacy. Made for everyone.

Collections

  • Everyday Essentials
  • Calculator Hub
  • Converter Hub
  • Text Studio
  • Business Toolkit
  • PDF Toolkit
  • Image Studio

Popular tools

  • AI Studio
  • Estimate Maker
  • Purchase Order Maker
  • Delivery Challan Maker
  • Invoice Maker
  • Quotation Generator

Company

  • All tools
  • About
  • Updates
  • Community
  • Analytics
  • Contact
  • Editorial policy
  • Privacy
  • Sitemap

Copyright © 2026 TechToolsCenter. All Rights Reserved.

Curated & Coded by Incinc Media Team

HomeTools
  1. Home
  2. Blog
  3. Developer
  4. What Is Semantic Versioning (SemVer)? MAJOR.MINOR.PATCH Explained
Developer September 20, 2026 11 min read

What Is Semantic Versioning (SemVer)? MAJOR.MINOR.PATCH Explained

A version number like 2.4.1 isn't arbitrary — under Semantic Versioning, each of the three numbers means something specific, and bumping the wrong one silently breaks trust with everyone depending on your package.

TCTechToolsCenter Team

On this page

  • The three numbers, and what each one means
  • Why this matters more than it looks like it should
  • Version range syntax — what `^` and `~` actually mean
  • Pre-release and build metadata
  • What actually counts as a "breaking change"
  • SemVer and CI/CD — where automated releases fit in
  • How Git tags relate to version numbers
  • SemVer isn't universal — some ecosystems do it differently
  • Common mistakes teams make with SemVer
  • A quick decision guide for your next release
  • How dependency resolution actually uses SemVer ranges
  • Lockfiles — how they interact with SemVer ranges
  • Deprecation — the step before a breaking removal
  • SemVer for APIs, not just packages
  • Changelogs — SemVer's necessary companion

Semantic Versioning (SemVer) is a convention for structuring a version number — MAJOR.MINOR.PATCH, like 2.4.1 — so that each part communicates something specific and predictable about what actually changed, letting anyone depending on your software decide, just by reading the version number, whether upgrading is safe or risky. Without a shared convention, a version bump is just an arbitrary label; with SemVer, a version number becomes a promise about compatibility that tooling (package managers, dependency resolvers) can actually rely on and act on automatically.

The three numbers, and what each one means

  • MAJOR — incremented when you make an incompatible, breaking change to the public API. Anyone upgrading across a major version boundary should expect to potentially need code changes on their end.
  • MINOR — incremented when you add functionality in a backward-compatible way. New features, but nothing that breaks existing usage — a safe upgrade for anyone already using the software correctly.
  • PATCH — incremented for backward-compatible bug fixes only. No new functionality, no breaking changes — just a fix, and the safest possible upgrade.

So a version going from 2.4.1 to 2.4.2 is a patch (a bug fix, safe to take immediately), 2.4.1 to 2.5.0 is a minor bump (new features, still safe), and 2.4.1 to 3.0.0 is a major bump (something broke compatibility, and upgrading needs care).

Sponsored

Why this matters more than it looks like it should

Modern package managers (npm, pip, Cargo, and most others) use version ranges to decide what to automatically install — a dependency declared as `^2.4.1` tells the package manager "any 2.x.x version at 2.4.1 or above is safe to auto-install," trusting that the maintainer follows SemVer correctly and won't ship a breaking change inside a minor or patch release. When a maintainer breaks this promise — shipping a breaking change as a minor or patch bump instead of a major one — every project that auto-updated within that supposedly-safe range can break unexpectedly, often in production, often without an obvious cause since nobody deliberately changed a version constraint. SemVer's entire value depends on maintainers actually following it correctly, not just formatting version numbers to look like it.

Version range syntax — what `^` and `~` actually mean

  • Exact (`2.4.1`) — only that exact version, no automatic updates at all.
  • Caret (`^2.4.1`) — allows minor and patch updates, but not major (`>=2.4.1 <3.0.0`). The most common default in npm's package.json.
  • Tilde (`~2.4.1`) — allows only patch updates (`>=2.4.1 <2.5.0`), more conservative than caret.
  • Wildcard/latest (`*` or `latest`) — allows any version at all, including breaking major upgrades automatically. Rarely a good default for a production dependency, since it removes any protection SemVer otherwise provides.
A subtlety many developers miss: for a 0.x.x version (before a project's first stable 1.0.0 release), SemVer treats the rules differently — a MINOR bump in the 0.x range is allowed to include breaking changes, and only PATCH is guaranteed backward-compatible. This is specifically why so many projects stay on 0.x for a long time: it signals "the API can still change," and npm's caret behavior for 0.x versions reflects this by treating the minor version as the effectively-breaking one instead of major.

Pre-release and build metadata

SemVer also defines an optional pre-release label, appended with a hyphen — `2.5.0-beta.1`, `2.5.0-rc.2` — for versions that precede a stable release and aren't guaranteed to be stable themselves. Pre-release versions sort before their corresponding stable release (`2.5.0-beta.1` is considered earlier than `2.5.0`), and package managers generally don't install pre-release versions automatically unless explicitly requested, precisely because they're signaling "not yet fully validated." Build metadata, appended with a plus sign (`2.5.0+build.20260920`), carries extra information (a build number, a commit hash) without affecting version precedence at all — two versions differing only in build metadata are considered equal for comparison purposes.

What actually counts as a "breaking change"

Deciding whether a given change deserves a major version bump is sometimes more judgment call than mechanical rule, but the general test is: would this change require any existing, correctly-written consumer code to be modified to keep working? Removing a function, changing a function's required parameters, changing what a function returns, or altering default behavior in a way existing callers relied on are all breaking changes. Adding a new optional parameter, adding a new function, or fixing behavior that was clearly documented as a bug (versus behavior consumers may have unintentionally come to rely on) usually aren't — though that last case, fixing a long-standing bug that some users have quietly depended on, is exactly where judgment calls get genuinely difficult and reasonable maintainers disagree.

SemVer and CI/CD — where automated releases fit in

Many teams automate version bumping as part of their CI/CD pipeline (see our CI/CD explainer for the broader pattern), using tools that inspect commit messages for a structured format (like Conventional Commits — `feat:`, `fix:`, `BREAKING CHANGE:`) to automatically determine whether the next release should be a major, minor or patch bump, then publish the release and update the changelog without a human manually deciding and typing the new version number. This removes a common source of human error — a maintainer manually forgetting to bump the major version for a breaking change made late on a Friday — but it only works correctly if commit messages are written accurately and consistently enough for the tooling to classify them correctly.

How Git tags relate to version numbers

It's common practice to tag a specific commit in Git with its release version (`v2.4.1`), giving a permanent, addressable pointer to the exact code that shipped as that version — distinct from a branch, which moves forward with new commits. Tools that automate SemVer releases typically create these tags automatically as part of the release process (see our Git merge vs rebase guide for how the underlying commit history that gets tagged is actually built), so anyone can check out `v2.4.1` later and get exactly the code that was published under that version, even if the branch it came from has since moved on with dozens of newer commits.

SemVer isn't universal — some ecosystems do it differently

Not every software project follows SemVer, and it's worth recognising the difference rather than assuming every X.Y.Z-looking version number carries the same guarantees. Some large software (browsers, operating systems) use date-based or purely sequential versioning (Chrome's version numbers, Ubuntu's YY.MM releases) with no compatibility promise implied by the numbers at all. Some projects follow "ZeroVer" — deliberately staying below 1.0.0 indefinitely even for mature, widely-used software — specifically to avoid ever promising the kind of long-term API stability a 1.0.0-and-beyond SemVer commitment implies. Before assuming a dependency's version numbers mean what SemVer says they should mean, it's worth briefly checking that project's own versioning policy, especially for a dependency your production system relies on heavily.

Common mistakes teams make with SemVer

  • Shipping a breaking change as a minor or patch release, breaking the trust that lets consumers safely auto-update within a version range.
  • Using an overly permissive version range (`*`, `latest`) for a production dependency, losing any protection SemVer would otherwise provide.
  • Assuming a 0.x.x dependency follows the same stability guarantees as a 1.0.0+ one, when SemVer explicitly treats pre-1.0 differently.
  • Manually bumping versions inconsistently across a team, rather than automating it from commit message conventions.
  • Not tagging releases in Git, making it hard to find the exact code that shipped as a specific published version later.
  • Treating an internal, private package the same as a public one — for something only your own team consumes, a stricter internal convention may matter less than for a package with thousands of unknown downstream consumers.

A quick decision guide for your next release

  1. Did you remove, rename, or change the behavior of anything a consumer might already depend on? → MAJOR.
  2. Did you add new functionality without touching any existing behavior? → MINOR.
  3. Did you only fix a bug, with no new functionality and no behavior change beyond "it now works as documented"? → PATCH.
  4. Is this release not yet fully validated (a beta, a release candidate)? → Append a pre-release label instead of publishing it as a plain stable version.

How dependency resolution actually uses SemVer ranges

When you install a package, your package manager doesn't just look at the one range you declared directly — it resolves ranges across your entire dependency tree, since your dependencies have their own dependencies, each with their own declared ranges. Modern package managers (npm, Yarn, pnpm) try to find a single set of versions that satisfies every range in the tree simultaneously, which is exactly why two unrelated packages can conflict: if Package A requires `lodash@^3.0.0` and Package B requires `lodash@^4.0.0`, no single lodash version satisfies both ranges (since the major version differs), and the resolver either fails outright or, in ecosystems that support it (npm, via nested node_modules), installs two separate copies of lodash at different versions to satisfy both consumers independently — invisible to you unless you go looking, but a real source of bundle bloat in a frontend project where every duplicated dependency adds real download weight.

Lockfiles — how they interact with SemVer ranges

A `package.json` (or equivalent) declares the *ranges* you're willing to accept, but a lockfile (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`) records the *exact* resolved versions that were actually installed the last time dependencies were resolved — this is what makes a fresh `npm install` reproducible across machines and CI runs, rather than potentially resolving to a different, newer version within the same declared range every time someone runs it. Committing the lockfile to version control (which most teams correctly do) means "it works on my machine" failures caused by two developers silently getting different patch/minor versions of the same dependency become far less common — everyone installs from the same locked, exact version set until someone deliberately runs an update and commits the new lockfile.

Deprecation — the step before a breaking removal

Responsible SemVer practice generally treats removing something as a two-step process rather than a single major-version surprise: first, mark the feature or function as deprecated in a minor release (often with a runtime console warning, and always in the changelog and documentation), giving consumers a release or two to migrate away from it with advance notice; only then, in a later major release, actually remove it. This gives downstream consumers a genuine window to react before their code breaks, rather than discovering the removal only when a major upgrade already breaks their build. Skipping the deprecation step and removing something directly in a major bump is technically SemVer-compliant (a major bump is exactly where breaking changes belong), but it's considerably less considerate toward consumers than giving them advance warning first — most well-maintained open-source projects treat the deprecation warning as a courtesy expectation on top of the bare SemVer rules.

SemVer for APIs, not just packages

The same MAJOR.MINOR.PATCH thinking is commonly applied to versioned web APIs, not just installable software packages — a REST API exposed as `/v1/`, `/v2/` in its URL path (or via a version header) is applying the same underlying idea: a major version bump signals to API consumers that the previous version's contract is no longer guaranteed and a new integration or migration effort may be needed, while additions within the same major version (`v2`) are expected to remain backward-compatible for existing integrations. The practical difference from a package is that an API typically has to keep multiple major versions running simultaneously for some transition period, since external consumers can't be forced to upgrade the way an internal dependency bump can be — deprecating and eventually sunsetting an old API version needs its own clearly communicated timeline, often measured in months rather than the days or weeks a package deprecation cycle might use.

Changelogs — SemVer's necessary companion

A version number alone tells a consumer *how risky* an upgrade might be, but not *what actually changed* — that's the changelog's job, and a project that bumps versions correctly but never documents what changed in each one is only giving half the useful information. A well-kept changelog, organized by version and grouped into categories like "Added," "Changed," "Fixed" and "Removed" (a widely used convention popularized by the "Keep a Changelog" format), lets a consumer deciding whether to upgrade read the actual list of changes rather than guessing from the version number alone whether a given minor bump added something relevant to them or not. Automated release tooling that derives version bumps from commit messages typically generates this changelog automatically from the same structured commit data, keeping the two in sync without extra manual effort on every release.

Tools used in this article

JSON FormatterBeautify, minify and validate JSON with error messages.Base64 Encoder / DecoderEncode text to Base64 or decode Base64 back to text.URL Encoder / DecoderPercent-encode or decode URLs and query parameters.UUID GeneratorGenerate secure random UUID v4 identifiers in bulk.

Sponsored

Frequently asked questions

MAJOR.MINOR.PATCH — MAJOR for breaking changes, MINOR for backward-compatible new features, and PATCH for backward-compatible bug fixes only.

TC

TechToolsCenter Team

Product & Tools

The team behind TechToolsCenter — building fast, private, browser-based tools and writing practical guides on how to get the most out of them.

Related articles

Developer 10 min

Git Merge vs Rebase: What's the Difference (and Which to Use When)

Both bring one branch's changes into another — the real difference is whether your history keeps an honest record of what actually happened, or gets rewritten into a straight line that never existed.

TechToolsCenter TeamRead
Developer 11 min

What Is a Vector Database? How AI Search and RAG Actually Store Data

A vector database doesn't look for an exact match — it finds the records whose meaning is closest to your query, which is exactly the capability that makes modern AI search, recommendations and RAG pipelines work.

TechToolsCenter TeamRead
Developer 10 min

What Is CI/CD? Continuous Integration and Continuous Deployment Explained

CI/CD automates the boring, error-prone parts of shipping code — running tests, building artifacts, deploying — so a change goes from commit to production the same reliable way every single time.

TechToolsCenter TeamRead

On this page

  • The three numbers, and what each one means
  • Why this matters more than it looks like it should
  • Version range syntax — what `^` and `~` actually mean
  • Pre-release and build metadata
  • What actually counts as a "breaking change"
  • SemVer and CI/CD — where automated releases fit in
  • How Git tags relate to version numbers
  • SemVer isn't universal — some ecosystems do it differently
  • Common mistakes teams make with SemVer
  • A quick decision guide for your next release
  • How dependency resolution actually uses SemVer ranges
  • Lockfiles — how they interact with SemVer ranges
  • Deprecation — the step before a breaking removal
  • SemVer for APIs, not just packages
  • Changelogs — SemVer's necessary companion

Sponsored