Versioning
Date: 2026-08-17
Naming releases so consumers know what a change will do to them. Semantic versioning promises a lot and delivers less, because “breaking” is judged by the publisher and experienced by you — and those two rarely agree.
Versioning assigns identifiers to releases so consumers can reason about upgrades.
Semantic versioning
MAJOR . MINOR . PATCH
2 . 4 . 1
MAJOR breaking change
MINOR new feature, backwards compatible
PATCH bug fix, backwards compatible
The promise: you can take any minor or patch upgrade without reading the changelog.
Why it under-delivers: “breaking” is a judgement the publisher makes about their intent, and consumers depend on behaviour the publisher never considered part of the contract.
A "PATCH" THAT BROKE SOMETHING
a bug fix — someone depended on the bug
a performance change — someone depended
on the timing
an error message change — someone parsed it
new validation — someone was sending
invalid data that used to pass
a changed sort order — nobody promised
it, everyone relied on it
Hyrum’s law names this: with enough consumers, every observable behaviour of your system will be depended on by somebody, regardless of what you promised.
Range specifiers, and the risk they carry
2.4.1 exact
~2.4.1 patch updates → 2.4.x
^2.4.1 minor updates → 2.x.x
* anything → never
^ is the npm default, which means a fresh install can pull code the author published minutes ago into your build. That’s the mechanism behind supply-chain incidents — the compromise doesn’t need to reach your repository, only the registry.
A lockfile is the control. It pins the entire resolved tree, so installs are reproducible and upgrades are deliberate rather than incidental. Commit it, and treat changes to it as reviewable — Dependency Management.
Versioning an API
URL PATH /v1/orders
visible, cacheable, obvious
ugly, and duplicates routes
HEADER Accept: application/
vnd.api.v2+json
clean URLs
invisible, harder to test and cache
QUERY PARAM /orders?version=2
easy
easy to omit accidentally
DATE-BASED 2026-04
← what Shopify, Stripe and Adobe use
no argument about what "breaking"
means; consumers pin a date and
migrate on a schedule
Date-based versioning has quietly won for platform APIs, and the reason is that it sidesteps the semver judgement entirely. There’s no debate about major versus minor — there’s a release, and a support window — Shopify, Reference - AEM.
Versioning things that aren’t code
DATABASE SCHEMA sequential migrations,
each with an up and
ideally a down
EVENT SCHEMAS a version on the event,
or additive-only change
DESIGN TOKENS renaming one is breaking
with NO compiler to catch
it
CONTENT / CONFIG usually unversioned, and
the source of "who changed
this?"
See: Database Migrations · Event Taxonomy Design · Design System Versioning
Practical rules
- Commit the lockfile. Non-negotiable
- Pin exactly in applications; use ranges in libraries. An application wants reproducibility; a library that pins exactly forces version conflicts on everyone who consumes it
- Read changelogs for major upgrades, and diff the lockfile for the rest
- Automate the boring upgrades — patch and minor, with tests as the gate. Batching six months of them into one afternoon is how upgrades stop happening
- Don’t version internal things prematurely. A service deployed alongside its only consumer doesn’t need an API version; it needs them deployed together
- Support one previous major for anything public, with a stated window
The version to carry
Semver is a communication convention, not a guarantee. It tells you the publisher’s intent, which is genuinely useful and is not the same as knowing your build will still work — which is what tests and a lockfile are for — Backwards Compatibility.