All articles

Workflows

Manage app versioning across both stores

Every mobile release carries two numbers: a version name users see and a build number the stores track. Here is how to version cleanly across iOS and Android without rejected uploads.

Nicolas Ristic·July 14, 2026·9 min read

App versioning works best when you treat it as two separate numbers: a public version name like 2.4.1 that users read in the store, and an internal build number that has to increase with every upload. The App Store and Google Play both use this two-number model, but they name the fields differently and enforce different rules. Get the two mixed up and you get rejected uploads, confusing changelogs, and releases you cannot tell apart. This guide explains both numbers, how semantic versioning fits, the exact iOS and Android fields, and how to keep the two platforms in sync.

What is the difference between a version name and a build number?

A version name is the human-readable label your users see on the store listing, such as 2.4.1. A build number is an internal counter that identifies one specific compiled binary, such as 1043. The version name is a marketing and communication tool: it tells people how big a change is and lets them talk about which version they are running. The build number exists for the stores and for you, so that every single upload is uniquely identifiable even when the public version name does not change. You will often ship several builds under the same version name, for example while fixing a crash found in review, and each of those builds needs its own higher build number even though users still see 2.4.1.

What is semantic versioning and how do you use it?

Semantic versioning is the widely used convention of writing your version name as three numbers, major.minor.patch, where each position signals the size of the change. You increase the major number for big or breaking changes and redesigns, the minor number for new features that do not break existing behaviour, and the patch number for bug fixes and small tweaks. So 2.4.1 means major version 2, the fourth feature release in that line, with one patch on top. This is a convention, not a store rule, but adopting it makes your history readable at a glance and helps everyone on the team agree on what a release contains before it ships.

  • Major (the first number): breaking changes, major redesigns, or a new paid version. Example: 1.9.0 becomes 2.0.0.
  • Minor (the middle number): new features that are backwards compatible. Example: 2.3.4 becomes 2.4.0.
  • Patch (the last number): bug fixes and minor corrections with no new features. Example: 2.4.0 becomes 2.4.1.

One habit worth keeping: reset the lower numbers when a higher one changes. When you bump the minor number, the patch goes back to 0, and when you bump the major, both the minor and patch reset. That is what keeps 3.0.0 reading as a clean new line rather than 3.4.7 out of nowhere.

How do the version fields work on iOS?

On iOS, the version name lives in a field called CFBundleShortVersionString, which App Store Connect and Xcode simply label Version. This is the public 2.4.1 style number your users see. The build number lives in a separate field called CFBundleVersion, labelled Build. Apple requires that within a single version name, each new build you upload has a higher build number, and it accepts several common formats such as a plain integer or a dotted form like 1043 or 1.0.43. When you submit a new version for review, you pick the specific build to attach to it. A useful detail: once a version name has been released on the App Store, you cannot reuse it for a different release, so treat your released version names as final.

How do the version fields work on Android?

On Android, the version name is called versionName and behaves like the iOS Version field: it is your public 2.4.1 string and it can be anything you want. The build number is called versionCode, and this is where Android is stricter than iOS. The versionCode must be a positive integer, with no dots, and it must always increase with every build you upload to a given track on Google Play. Google Play uses versionCode alone to decide which build is newer, so a build with a higher versionCode is always treated as the update. If you forget to raise it, the Play Console rejects the upload outright. Because it is an integer, teams often derive it from a build counter or from a date, but the only hard requirement is that it never goes backwards.

  • versionName: the public string, for example 2.4.1. Shown to users, free-form, does not have to be numeric.
  • versionCode: a positive integer, for example 1043. Never shown to users, must strictly increase, and is how Google Play ranks builds.
  • Both live in your build config (build.gradle for native Android, or pubspec.yaml for Flutter where the format is versionName+versionCode, such as 2.4.1+1043).

Should you keep iOS and Android version numbers in sync?

Keeping the version name identical on both platforms is a good habit that pays off constantly. When a user reports a bug on 2.4.1, you want that to mean the same release on iOS and Android, not two different code states. When your support team, your changelog, and your analytics all key off one version name, cross-platform debugging gets dramatically simpler. The build numbers, on the other hand, do not need to match, because iOS and Android increment them independently and the stores never compare them. So the practical rule is: share the marketing version name across both stores, and let each platform manage its own build number. If you build with Flutter or React Native from one codebase, you are already producing both apps from the same source, which makes a shared version name the natural default.

How do you write good release notes for each version?

Release notes, often called the What's New text, are the short description attached to each version explaining what changed. Both stores show them on the listing and in the update screen, so they are read far more often than developers assume. Good notes are specific and user-facing: they describe what the person gets or what got fixed, not internal ticket numbers or refactors. Lead with the change users will notice most, keep each line short, and skip filler like generic bug fixes and performance improvements when you actually have something concrete to say. Because both stores support localized release notes, translating them for your main markets is a cheap way to make an update feel considered rather than shipped and forgotten.

  1. Open with the headline change, the one thing most users will care about.
  2. List notable additions or fixes in plain language, one per line.
  3. Match the tone across iOS and Android so a user sees the same story on both.
  4. Localize the notes for your top markets, the same way you localize the listing.
  5. Keep an internal changelog too, so version 2.4.1 always maps to a known set of commits.

What are staged and phased rollouts?

Both stores let you release a new version to a slice of your users first, then widen it, so a bad build does not reach everyone at once. On Google Play this is called a staged rollout: you choose a percentage, for example 10 percent, and Google delivers the update to that share of users, letting you increase the percentage or halt the rollout if crash rates spike. On the App Store the equivalent is phased release, which automatically rolls an automatic update out over a seven-day window in increasing steps, with an option to pause. In both cases users can still get the latest version immediately if they update manually from the store, so a phased rollout slows automatic updates rather than hiding the release. For any update touching risky code, turning this on gives you a safety valve.

What are the most common versioning mistakes?

Most versioning pain comes from a short list of avoidable mistakes, and nearly all of them surface at upload time when you least want the delay. Knowing them in advance saves a rejected build during a release you were trying to ship quickly.

  • Forgetting to bump the Android versionCode, so Google Play rejects the .aab because the number already exists. This is the single most common release-day blocker.
  • Reusing an iOS build number under the same version name, which App Store Connect refuses the same way.
  • Letting the iOS and Android version names drift apart, so 2.4.1 on iOS is not the same code as 2.4.1 on Android and bug reports become ambiguous.
  • Hand-editing versions in the build file and forgetting one platform, which is easy when the two numbers live in different files.
  • Shipping a version name that goes backwards or skips around, which is not blocked by the stores but confuses users and your own changelog.

The reliable fix is to make the version bump part of your release process rather than something you remember by hand. Whether you script it in CI or manage it in a release platform, the goal is that the correct version name and an always-increasing build number are set the same way every time, for both stores, so a rejected upload never costs you an hour on release day. If you build cross-platform, see how the iOS side of that pipeline works for Flutter and for React Native.

What is the difference between versionCode and versionName?

versionName is the public string users see, like 2.4.1, and it can be anything. versionCode is a positive integer, like 1043, that users never see and that Google Play uses to decide which build is newer. The versionCode must increase with every upload, while the versionName can repeat or follow any scheme you choose. They are the Android equivalents of the iOS Version and Build fields.

Can two app versions have the same build number?

No, not within the same store track. On Google Play the versionCode must be strictly higher than the previous upload, and on the App Store the build number must be higher within a given version name. You can keep the public version name the same across several builds, for example while fixing a review rejection, but each of those builds needs its own higher build number to be accepted.

Do iOS and Android versions have to match exactly?

The stores do not require it, and they never compare the two platforms. Matching the public version name across iOS and Android is a strong convention because it makes bug reports, analytics, and support unambiguous. The build numbers, however, are managed independently per platform and do not need to match, since each store tracks its own counter and never looks at the other.

How do you roll back a bad app release?

You cannot truly un-ship a version, since neither store lets you replace a released build with older code under the same version. If you caught it during a staged or phased rollout, halt the rollout to stop new users getting it. Then fix the bug and publish a new, higher version. This is why a fast, correct version bump matters: your recovery path is always forward, never backward. Keeping your legal and store metadata versioned the same way avoids similar surprises.

Written by

Nicolas Ristic

Nicolas builds Releasely and writes about App Store submission, ASO, and shipping Flutter and React Native apps to the App Store and Google Play.

ShareXLinkedIn
Releasely

Ship to both stores from one workspace

Drag a build or a project folder, write release notes, push to App Store Connect and Play Console. Free to start.