TL;DR
- Versions are MAJOR.MINOR.PATCH (
1.2.3).- MAJOR: breaking API changes.
- MINOR: new, backward‑compatible features.
- PATCH: backward‑compatible bug fixes.
- Pre‑releases add
-alpha.1,-beta.2,-rc.1and sort before the final release. Build metadata+metais ignored for precedence. - For libraries: don’t break on MINOR/PATCH. For apps: be stricter than ranges; rely on lockfiles and tested upgrades.
0.xis unstable: treat MINOR as potentially breaking. Reach 1.0.0 once your API is dependable.
1) What counts as “the API”?
Anything your users depend on: exported functions/classes, CLI flags/exit codes, config keys, environment variables, HTTP endpoints and payloads, file formats, side effects. If changing it can break existing consumers, it’s a breaking change → MAJOR.
2) Pre‑release & build metadata (the small print)
1.4.0-alpha.2 < 1.4.0-beta.1 < 1.4.0-rc.1 < 1.4.0
1.4.0+build.7 == 1.4.0 # +meta doesn’t affect ordering
Use pre‑releases for testing with early adopters. Don’t promise stability until the final .0 is out.
3) Version ranges you’ll actually see
| Notation | Means (roughly) | Good for |
|---|---|---|
| ^1.2.3 | >=1.2.3 <2.0.0 | Auto‑get compatible MINOR/PATCH |
| ~1.2.3 | >=1.2.3 <1.3.0 | Only PATCHes (and tiny MINOR in some ecosystems) |
| 1.2.x | >=1.2.0 <1.3.0 | Lock to a MINOR line |
| >=1.2.3 <2 | Explicit range | Precise policies |
| * / latest | anything | Don’t do this in prod |
0.x special case (npm‑style):
^0.2.3→>=0.2.3 <0.3.0(caret stops at next MINOR).^0.0.3→>=0.0.3 <0.0.4(caret stops at next PATCH).
Libraries should prefer narrow ranges and test matrixes; apps should pin via lockfile and update intentionally.
4) Maintainers: how to bump correctly
- PATCH: bug fix, perf, doc typo, build tweaks, internal refactors with no API change.
- MINOR: new feature, new endpoint/field (additive), deprecations without removal.
- MAJOR: removals, behavior changes, signature changes, renamed fields, dropped platforms. Provide a migration guide and deprecate first when possible.
Release hygiene
- Automate release notes from commits (e.g., Conventional Commits).
- Run contract tests and schema diff checks to catch accidental breaks.
- If you accidentally break on MINOR/PATCH, own it: publish a revert PATCH or hot MAJOR + notice.
5) Consumers: upgrade without fear
- Treat MAJOR bumps as migration work; read changelogs.
- Allow MINOR/PATCH auto‑updates only with CI that runs tests.
- Use lockfiles (
package-lock.json,yarn.lock,pnpm-lock.yaml,Cargo.lock,Pipfile.lock) to keep fleets reproducible. - In APIs, version URLs or headers (
/v2,Accept: application/vnd.example.v2+json).
6) Quick examples
Library adds optional param (back‑compat):
- greet(name: string): string
+ greet(name: string, opts?: { excited?: boolean }): string
# → MINOR
API removes field or changes type:
- { "price": "9.99" } // string
+ { "price": 9.99 } // number
# → MAJOR (clients may break)
Bug fix only:
- off-by-one in pagination
+ correct lastPage calculation
# → PATCH
7) Common pitfalls & fixes
| Pitfall | Why it burns | Fix |
|---|---|---|
| Shipping breaking changes as MINOR/PATCH | Downstream outages | Bump MAJOR, add deprecations & codemods |
| Relying on latest tags | Non‑reproducible builds | Pin and use lockfiles |
| “Private” APIs leaking | Users depend on internals | Mark internal APIs; use @internal/underscores; enforce lint |
| 0.x forever | No stability guarantees | Cut 1.0.0 when API settles |
| Hidden schema breaks | JSON shape shifts silently | Contract tests, schema diff in CI |
8) Quick checklist
- [ ] Bump MAJOR for breaking, MINOR for additive, PATCH for fixes.
- [ ] Use pre‑releases for testing; remove the tag for GA.
- [ ] Maintainers: publish changelogs and migration notes.
- [ ] Consumers: rely on lockfiles and CI to validate updates.
- [ ] Version public APIs explicitly (
/v2, media types).
One‑minute adoption plan
- Decide what your API is and write it down (exports, endpoints, formats).
- Automate release notes and semantic bumping from commits/tests.
- Start publishing pre‑releases for risky changes; gather feedback.
- Pin dependencies with a lockfile; run CI on update PRs.
- Tag a stable 1.0.0 once you’re confident in your contract.