For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Versioning

Why mobile apps need marketing versions and build numbers, when OTA is enough, and how that maps to store releases.

Mobile users do not pull every commit. They install a binary, keep it for weeks or months, and only move forward when the store (or an over-the-air update) gives them something new. Versioning is how you tell stores, devices, and support which contract that binary is on.

Get it wrong and uploads are rejected, OTA updates land on incompatible native code, or you cannot answer "which build crashed?"

Two numbers, two jobs

Almost every mobile release talks about two identifiers. Mixing them up is the usual failure mode.

Marketing / user-facing versionBuild number (iOS) / version code (Android)
Looks like1.6.247
JobWhat humans and store listings call this releaseStrictly increasing integer so each upload is unique
Changes whenYou intend a new "Version X" for usersEvery store upload, even for the same marketing version

You can ship build 48, 49, 50 all labeled 1.6.2 while you iterate on a release candidate. You cannot upload a lower build number than one the store already accepted for that app.

Why it is stricter than web

  • Store gates - Apple and Google reject duplicate or decreasing identifiers. There is no "just redeploy".
  • Long-lived binaries - old installs stick around. Features, API assumptions, and force-upgrade policies all key off version.
  • Native vs JavaScript - a JS-only fix can often ship over the air. A new native module, SDK bump, permission, or icon usually needs a full store binary.
  • Support and crashes - device + OS + app version is the minimum useful bug report.

So versioning is part of your product contract with the OS stores and with users who will not all upgrade on day one.

OTA updates vs a new store version

Think in release types, not only in numbers:

Kind of changeTypical path
UI, copy, pure JS fixesOver-the-air update on the same marketing version / runtime
Native code, new native dependency, permissions, splash/icon, Expo SDKNew store binary. Bump the marketing version when you want users (and OTA runtime) to move to that contract
UnsureAssume store release

OTA is fast and does not need review, but it only stays safe if the update is tied to a runtime that matches the native binary. When you bump the marketing version and ship a new binary, older OTA channels should not silently apply to that new install (and the reverse). That split is intentional.

Habits that pay off

  • Decide the marketing version before you cut the store build you will submit.
  • Write store "What's New" for humans. Reviewers and users both read it.
  • Plan for stragglers: what is the oldest version you still support, and when do you force upgrade?
  • Keep mobile versions on their own cadence. They will not line up with every web deploy, and that is fine.

In TurboStarter

Expo config holds the user-facing version. Production EAS builds use a remote version source with autoIncrement so iOS build numbers and Android version codes advance without hand editing. runtimeVersion uses the appVersion policy, so OTA bundles stay scoped to that marketing version.

When you need a new store binary, bump version in apps/mobile/app.config.ts and keep apps/mobile/package.json aligned, then follow publishing. For JS-only fixes on an existing binary, use OTA updates without bumping.

If you are importing an app that was already live, seed EAS with eas build:version:set once so the next auto-increment does not collide with store history.

Upstream: App versions on EAS.

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter