A Practical Guide to Semantic Versioning

SemVer’s MAJOR.MINOR.PATCH format looks trivial, but getting it wrong breaks dependent projects in ways that erode trust fast. Understanding what each number actually promises matters whether you’re publishing a package or just deciding how to tag your next release.

What Each Number Means

  • MAJOR — incremented for breaking changes. Anything that could make existing code stop working.
  • MINOR — incremented for new, backward-compatible functionality.
  • PATCH — incremented for backward-compatible bug fixes only.
1.0.0 -> 1.0.1   # bug fix, no API changes
1.0.1 -> 1.1.0   # new feature added, old code still works unchanged
1.1.0 -> 2.0.0   # breaking change — old code may need updates

Version Ranges in package.json

{
  "dependencies": {
    "express": "^4.18.0",
    "lodash": "~4.17.21",
    "react": "18.2.0"
  }
}

^4.18.0 allows any 4.x.x update (minor and patch), ~4.17.21 allows only patch updates within 4.17.x, and a bare 18.2.0 pins the exact version with no automatic updates at all.

The Trust Contract

SemVer is fundamentally a promise to your consumers: “if you only pull minor and patch updates, your code will keep working.” Breaking that promise — shipping a breaking change as a patch release — is how dependency updates end up silently breaking production for everyone downstream, and why teams eventually stop trusting your version numbers at all.

Pre-release and Build Metadata

2.0.0-alpha.1    # pre-release, sorts before 2.0.0
2.0.0-beta.3
2.0.0-rc.1       # release candidate
2.0.0            # the actual stable release