How to Version a Shared Foundation Without Breaking Apps
Versioning a shared foundation across many products means additive changes, deprecation windows, and pinned versions. Here is how to upgrade without breaking apps.
The moment a foundation is shared by more than one product, changing it becomes dangerous. A one-line edit that helps product A can break products B through T while you sleep. The way you version a shared foundation decides whether that edit is a routine push or a portfolio-wide incident. My rule: additive by default, breaking changes are rare and scheduled, and every product pins the foundation version it runs. That is the whole game.
How do I change a shared foundation without breaking every app?
Make changes additive whenever you can. Add a new function next to the old one. Add an optional parameter with a safe default. Add a new field that existing callers ignore. Additive changes are invisible to every product that has not asked for them, which means you can ship them the second they are ready with zero coordination.
Breaking changes are the enemy of a shared layer, so you treat them as expensive on purpose. A breaking change is anything that forces a consumer to edit code: a renamed function, a removed parameter, a changed return shape. In a portfolio, that edit multiplies by the number of products. So before you make one, ask whether an additive path exists. It almost always does, and it is almost always worth the small ugliness of keeping the old thing around.
This is the discipline that keeps a foundation from slowing you down. A foundation feels slow only when every change requires touching every app. Additive versioning breaks that link.
Pin the version each product runs
No product should float on the latest foundation automatically. Each one pins a version and upgrades on its own schedule. Pinning is what turns the foundation from a live wire into a stable dependency. Product A can run last month's foundation while product B runs today's, and neither can hurt the other.
Without pinning, you have accidental coupling: a change meant for one product silently ships to all of them the next time they deploy. That is how a small portfolio turns into a house of cards. Pinning costs you a version number in each product's config and buys you the right to upgrade one product at a time, verify it, and move on. It also keeps the blast radius of a single change contained to whatever you deliberately upgraded.
The tradeoff is drift. If products lag too far behind, you end up supporting five foundation versions at once. So pinning comes with a rule: nobody trails the current version by more than two releases. Upgrade laggards on a cadence, not on a crisis.
Give breaking changes a deprecation window
Sometimes a breaking change is genuinely worth it. When it is, you never rip the old thing out in the same release. You do it in three moves.
- Add the new path alongside the old one. Both work. Nothing breaks yet.
- Deprecate the old path with a loud warning and a date. Every product that still uses it gets a clear signal and time to move.
- Remove the old path only after every product has migrated and the window has passed.
The window is what makes it safe. It decouples "I built the new thing" from "every product adopted it," and in a solo portfolio those two events can be weeks apart. I lean on my build platform to find every call site of the deprecated path fast, so Bootspring tells me which of the twenty products still need to move before I pull the old code. Deprecation without a way to see the consumers is just hope.
Test the foundation against real consumers
Version discipline means nothing if you cannot tell whether a change broke something. The foundation needs its own test suite, and ideally a smoke test that runs each product's critical path against the new version before you tag it. This is the same instinct behind treating your internal platform as a real product with its own quality bar, not a pile of helpers nobody owns.
I keep a thin integration check per product that exercises auth, a database write, and a deploy. If the new foundation version passes those across the portfolio, I tag it. If it fails one, I know exactly which product and exactly which surface before anything reaches production.
The payoff: cheap upgrades forever
Get versioning right and the foundation keeps its promise. Each new product is cheaper to ship than the last because it inherits a stable, well-versioned base instead of a moving target. You upgrade on your terms, one product at a time, with a way to see and test every consumer. That is the difference between a foundation that compounds and a foundation that becomes the thing you are most afraid to touch.