Skip to main content

Semantic Versioning (SemVer): MAJOR.MINOR.PATCH Explained

Semantic Versioning is the contract between library authors and consumers. Version numbers communicate intent: does upgrading break my code? This guide explains exactly when to increment each segment, how pre-release identifiers work, and how to automate versioning with Conventional Commits — so your changelogs write themselves.

✓ MAJOR.MINOR.PATCH decision rules ✓ Pre-release & build metadata ✓ Conventional Commits → version mapping ✓ Changelog automation tools ✓ SemVer vs CalVer comparison
Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

SemVer Format & Decision Rules

Given a version number MAJOR.MINOR.PATCH, increment each part based on the nature of the change. Reset lower segments to zero when incrementing a higher one.

# PATCH: backward-compatible bug fix
# Before: 1.4.2  →  After: 1.4.3
function getUserById(id: string): User | null {
  // Fixed: was throwing instead of returning null for missing users
  return db.users.find(u => u.id === id) ?? null;
}

# MINOR: new backward-compatible feature
# Before: 1.4.3  →  After: 1.5.0
function getUserById(id: string, options?: { includeDeleted?: boolean }): User | null {
  // New: optional includeDeleted parameter added
  const query = options?.includeDeleted ? db.users : db.users.where({ deleted: false });
  return query.find(u => u.id === id) ?? null;
}

# MAJOR: breaking change (changed return type)
# Before: 1.5.0  →  After: 2.0.0
async function getUserById(id: string): Promise<User> {
  // Breaking: now async, throws UserNotFoundError instead of returning null
  const user = await db.users.findById(id);
  if (!user) throw new UserNotFoundError(id);
  return user;
}

Pre-Release Versions & Build Metadata

Use pre-release identifiers for software that is not yet stable. Precedence order: alpha < beta < rc < release. Build metadata is ignored during version comparison.

# Pre-release version progression
1.0.0-alpha        # early internal testing
1.0.0-alpha.1      # second alpha build
1.0.0-beta.1       # external testing begins
1.0.0-rc.1         # release candidate — feature freeze
1.0.0              # final release

# Precedence (lower to higher):
# 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta
# < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0

# Build metadata (IGNORED during comparison)
1.0.0+20241201.sha.abc1234    # date + git SHA
1.0.0+build.42                # CI build number
# 1.0.0+build.42  ==  1.0.0+build.99  (same version, different builds)

Conventional Commits → Version Bump Mapping

Conventional Commits encode the version bump type directly in the commit message. Tools like semantic-release and release-please read these to automate releases entirely.

# MAJOR bump — breaking change (two ways to signal)
feat!: change getUserById to async with error throwing
# OR footer notation:
fix: correct edge case in user lookup

BREAKING CHANGE: getUserById now throws UserNotFoundError instead of returning null

# MINOR bump — new feature
feat: add includeDeleted option to getUserById
feat(api): add batch user lookup endpoint

# PATCH bump — bug fix, performance, refactor
fix: prevent null pointer when user id is empty
perf: cache user lookup results for 5 minutes
refactor: extract user mapper to separate module

# No version bump (ignored by release tools)
docs: update getUserById JSDoc examples
test: add edge case tests for getUserById
chore: upgrade dev dependencies
ci: add caching to GitHub Actions workflow

SemVer vs CalVer Comparison

Dimension SemVer (1.2.3) CalVer (2024.03.15)
Signals Breaking changes & new features Release date & cadence
Best for Libraries, APIs, packages OSes, apps, tools with fixed schedules
Examples React 18.2.0, Django 4.2.1 Ubuntu 24.04, pip 24.0
Automation semantic-release, release-please Date-based scripts
Ecosystem support npm, PyPI, Cargo, RubyGems Project-specific (no standard tooling)

Changelog Generation Tools

  • ✓semantic-release — Fully automated: reads commits, determines version, publishes to npm/GitHub, updates CHANGELOG.md. Zero configuration for projects following Conventional Commits. Best for libraries.
  • ✓release-please (Google) — Creates a Release PR that accumulates all changes since the last release. You control when to merge. Supports multi-package monorepos. Best for apps and teams wanting a manual merge gate.
  • ✓standard-version — Local CLI that bumps version, updates CHANGELOG.md, and creates a git tag. No CI integration required. Good for teams starting out.
  • ✓git-cliff — Rust-based, highly customizable changelog generator. Supports custom templates (Tera syntax) and any commit format. Fast and configurable for non-standard commit styles.

Frequently Asked Questions

When should I increment MAJOR vs MINOR vs PATCH?▼

Increment PATCH (1.0.0 → 1.0.1) for backward-compatible bug fixes only — no new features, no API changes. Increment MINOR (1.0.0 → 1.1.0) when you add new functionality in a backward-compatible way — new functions, new optional parameters, new endpoints. Increment MAJOR (1.0.0 → 2.0.0) for any breaking change — removing a function, changing a function signature, changing the API response format, or dropping support for a language version.

What does 0.x.y versioning mean?▼

Versions below 1.0.0 are considered initial development. The API is considered unstable — anything may change at any time. In 0.x.y, the MINOR version behaves like MAJOR: breaking changes can happen on MINOR bumps. Once your public API is stable and ready for production use, release 1.0.0. Many libraries stay at 0.x for years; do not assume 0.x means abandoned or immature.

How do pre-release versions work in SemVer?▼

Append a hyphen and identifier: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-beta.2, 1.0.0-rc.1. Pre-release versions have lower precedence than the release: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0. Use alpha for early internal testing, beta for external testing with known issues, and rc (release candidate) for the version that will become the final release unless critical bugs appear.

What is build metadata and when is it used?▼

Build metadata is appended with a plus sign: 1.0.0+20241201, 1.0.0+sha.abc1234. It carries information about the specific build — date, git SHA, build number — but has NO effect on version precedence. Two versions with different build metadata are considered equal for dependency resolution purposes (1.0.0+build1 == 1.0.0+build2). It is primarily used for traceability in enterprise environments.

How does SemVer map to Conventional Commits?▼

Conventional Commits provides a machine-readable commit format that maps directly to SemVer increments. Commits with 'BREAKING CHANGE:' in the footer or '!' after the type (feat!:, fix!:) trigger a MAJOR bump. Commits starting with 'feat:' trigger a MINOR bump. Commits starting with 'fix:', 'perf:', 'refactor:', etc. trigger a PATCH bump. Tools like semantic-release and release-please read your git history and automatically determine and apply the correct version bump.

SemVer vs CalVer — which should I use?▼

SemVer (MAJOR.MINOR.PATCH) is best for libraries and APIs where communicating breaking changes to consumers is essential — it tells users exactly what kind of change to expect. CalVer (calendar versioning, e.g. 2024.1, 2024.03.15) is better for applications, OS distros, and tools where the release date is more meaningful than API stability (Ubuntu uses YY.MM). Use SemVer for any package published to npm, PyPI, or similar registries. Use CalVer for end-user software with predictable release schedules.