10+ AI SaaS templates for web & mobile
home

Software versioning for web, mobile, and browser extensions

·10 min read

Software versioning strategies for SaaS: SemVer, app versioning on stores, OTA vs binaries, and how to version web, mobile, and extensions without chaos.

Most teams treat software versioning as a package.json chore until a store rejects an upload, a crash report says "unknown build," or support asks which release a customer is on. Then it suddenly matters.

Libraries have Semantic Versioning. Mobile stores have marketing versions and build numbers. Browser extensions burn a version string forever once you upload it. Web apps can redeploy silently and skip the whole conversation until something breaks in production.

If you ship a SaaS across more than one surface, you need a strategy that fits each platform without forcing one fake "v1.0.0 everywhere" policy.

Short answer

Software versioning is how you label a release so humans, stores, and tooling can tell builds apart. Use SemVer as a shared language (MAJOR.MINOR.PATCH), then adapt per platform: a product version for web, marketing version + monotonically increasing build numbers for mobile, and a never-reuse manifest version for browser extensions. Keep surfaces independent when release cadences differ. Tie OTA mobile updates to a runtime, not to every git commit.

Quotable definition: Software versioning assigns a structured identifier to a shippable cut of your product so support, stores, analytics, and rollbacks can refer to the same thing.

Why software versioning still matters

Git already has SHAs. Hosts already have deploy IDs. Those are engineer-facing. Versioning is the product-facing label.

You want it for:

JobWithout a versionWith a version
Support"I refreshed yesterday""I'm on 1.8.2"
MonitoringGroup by hour and prayGroup by release
StoresRejected upload / silent failAccepted, attributable build
RollbacksGuess which deployRestore a named cut
ChangelogsVague "latest"Diff people can read

SemVer is the default vocabulary because it encodes kind of change, not just sequence. From the SemVer 2.0.0 spec:

  • MAJOR - incompatible API (or, for apps, changes users must treat as a new contract)
  • MINOR - backward-compatible functionality
  • PATCH - backward-compatible fixes

For end-user apps the "public API" is fuzzier than a library. Treat installation requirements, permissions, and breaking UX as the contract users and stores care about. The number still works as communication even when you are not publishing npm packages.

Three versioning strategies (pick intentionally)

Teams usually land on one of these. Mixing them without documenting the rule is how monorepos get confusing.

1. Strict SemVer for shared packages

Best for libraries, SDKs, and anything with downstream consumers who read changelogs programmatically. Breaking export? Major. New optional API? Minor. Bugfix? Patch.

In a Turborepo, that often means @workspace/* packages can stay on their own versions (or 0.x internally) while apps carry the product versions users see.

2. Product / marketing SemVer for apps

Best for web apps, mobile store listings, and extensions. 2.4.0 means "this is the product release," not "this npm graph moved." Patch/minor/major still guide the team; the audience is support and users, not npm install.

3. CalVer or build-only labels

Date-based (2026.07.29) or sequential build IDs work for internal tools and some web-only products. They are weaker for communicating break risk. Fine as a deploy stamp alongside a human product version; weak as the only public label if you also ship mobile and extensions.

Rule of thumb: one human product version per deployable app, plus whatever opaque integers the platform demands underneath.

How each platform supports (and constrains) versioning

This is where generic "just use SemVer" advice falls over.

Web

Web is the freest platform. Nothing stops you from shipping without bumping a number. That is also why web teams skip it.

What good looks like:

  • A single product version for the app users meet
  • That value shown somewhere boring (footer, about screen) and sent to error/analytics tools
  • Bump when the release is worth naming, committed with the production deploy
  • Git SHA or host deployment ID as the engineering breadcrumb in addition, not instead

You do not get store rejection emails. You do get "which build had the checkout bug?" emails. Versioning is how you answer them. Deeper kit notes: web versioning recipe.

Mobile (iOS and Android)

Mobile is stricter because Apple and Google gate uploads, and users keep old binaries for months.

You almost always manage two identifiers:

IdentifierExampleJob
Marketing / user-facing version1.6.2What humans and store listings show
Build number (iOS) / version code (Android)47Strictly increasing integer per upload

You can upload builds 48, 49, 50 all labeled 1.6.2 while iterating a release. You cannot reuse a lower build number the store already accepted.

Mobile also splits release types:

ChangeTypical path
JS/UI-only fixesOver-the-air update on the same marketing version / runtime
Native modules, SDK bumps, permissions, iconsNew store binary; bump marketing version when the native contract changes

OTA is not a free pass to ignore versioning. Updates must stay compatible with the native binary they land on (Expo's runtimeVersion is one way to encode that; see EAS app versions and our mobile updates guide). Practical playbook: mobile versioning recipe.

Browser extensions

Extensions sit closer to mobile than web. Chrome, Edge, and Firefox treat the manifest version as a one-shot id for that listing. Reuse a version and the upload fails. Decrease it and you fight the store.

That means:

  • Every zip you ship needs a new version string
  • Burned a bad upload? Bump again. History does not forget
  • Permission or host-access changes raise review cost; encode that in at least a minor bump and say so in listing notes

Frameworks like WXT typically derive manifest version from package.json so you do not maintain two sources. Kit notes: extension versioning recipe.

Cross-platform SaaS: do versions have to match?

Short answer: no.

A Next.js deploy every day, an App Store review every two weeks, and an extension permission review that takes longer will never share one number honestly. Forcing web === mobile === extension creates fake alignment and delayed releases.

What should stay aligned instead:

  • API contracts and auth/billing rules (one backend; see cross-platform SaaS with one backend)
  • Support playbooks that ask for platform + version (iOS 1.6.2 (build 50), extension 1.2.5, web 1.8.2)
  • Minimum supported client versions when you break the API on purpose

Independent product versions per app, shared backend versioning discipline for the API you actually break.

A practical strategy you can run Monday

  1. Name the product version for each app (apps/web, apps/mobile, apps/extension in a monorepo).
  2. Agree SemVer meanings for apps: patch = fix, minor = feature, major = breaking UX / permissions / install requirements.
  3. Automate platform integers where possible (remote build numbers on mobile; never hand-edit if your CI already increments).
  4. Decide OTA vs store before you bump mobile. JS-only → OTA. Native → store + version bump.
  5. Ship notes with every store/extension upload. Reviewers and users read them; future-you does too.
  6. Attach the version to telemetry so crashes and funnels group by release, not by calendar luck.
  7. Document the oldest client you still support so API deprecations are not surprises.

Feature flags pair well with this. A version says what binary or bundle someone has; a flag says which experiment they see inside it. Do not replace versioning with flags. Use both.

How we approach it in TurboStarter

TurboStarter is a multi-platform monorepo (Next.js web, Expo mobile, WXT extension) with one backend. Versioning follows the platform constraints above instead of one global counter:

  • Web - product version in apps/web/package.json, exposed via appConfig, shown in the footer
  • Mobile - user-facing version in Expo config, EAS remote appVersionSource with production autoIncrement, runtimeVersion policy appVersion so OTA stays scoped
  • Extension - version in apps/extension/package.json; WXT writes the manifest; stores get a fresh number per upload

Same SemVer language, different enforcement. Recipes go deeper per surface: web, mobile, extension. For the broader architecture, the Next.js AI SaaS starter kit and React Native SaaS starter pages show how those apps sit in the monorepo.

Mistakes that waste weeks

  1. One version for the whole monorepo - a docs typo bump should not force an App Store submission.
  2. Bumping mobile version to push an OTA - you just changed the runtime contract; old binaries may never see the update you meant.
  3. Reusing extension versions - the store remembers; rebuild with a new number.
  4. Only tracking git SHAs on web - fine for engineers, useless for most support channels.
  5. Ignoring permissions as a versioning event - extension and mobile trust-boundary changes deserve a louder bump than a CSS fix.
  6. Skipping release notes - future debugging becomes folklore.

Frequently asked questions

Sounds good?Now let's make it real. In minutes.
Try TurboStarter

Takeaway

Software versioning is not bureaucracy. It is how multi-platform products stay debuggable when web redeploys daily, mobile ships through stores, and extensions burn version strings forever. Use SemVer as the shared dialect, respect each platform's constraints, keep app versions independent when cadences diverge, and wire the label into UI, telemetry, and release notes so the number means something when something breaks.

world map
Community

Connect with like-minded people

Join our community to get feedback, support, and grow together with 600+ builders on board, let's ship it!

Join us

Ship your startup everywhere. In minutes.

Skip the complex setups and start building features on day one.

Get TurboStarter