How to ship to the app stores

Shipping

How to version and name an app

Two conventions that cost nothing to get right at the start and are genuinely painful to fix later.

  1. The build number must always be higher than the last one.

    Every upload needs a build number strictly greater than any you have already sent -- including rejected builds, expired betas and ones you deleted. There is no reusing a number. Bump before every single upload, without thinking about it.

  2. Version and build are two different numbers.

    The version is what users see (1.8.2). The build is the upload counter (86). In Flutter both live in one line of pubspec.yaml as version: 1.8.2+86, which becomes CFBundleShortVersionString and CFBundleVersion on iOS, and versionName and versionCode on Android.

  3. Keep one source of truth for both platforms.

    Setting the number in Xcode and again in Gradle guarantees they drift. Set it once in the project file your build reads, and let both platforms derive from it.

  4. Android version codes are permanent and integer-only.

    A versionCode is a plain increasing integer that can never be reused, even after unpublishing. Some teams derive it from a timestamp or a CI build number for exactly this reason.

  5. TestFlight builds consume numbers too.

    Every beta you push burns a build number. If you test properly you’ll be at build 80 before version 1.0 ships, and that is entirely normal -- the build number is not a measure of anything.

  6. Bump the version for humans, the build for machines.

    Users read the version to know whether something changed. Reserve a version bump for a release with something in it, and let the build number absorb every upload in between.

  7. The bundle identifier is forever.

    com.braindex.app is permanent once published -- changing it means a new app, a new listing and no upgrade path for existing users. Choose reverse-DNS, keep it lowercase, and never encode a version or a year in it.

  8. Pick a name you can actually own in search.

    A common word is unfindable in a store search box. A distinctive coined name is easy to rank for and harder to explain -- the usual answer is a distinctive name plus a descriptive subtitle.

  9. Check the name is free everywhere before you commit.

    Both stores, the domain, the obvious social handles, and a trademark search. Renaming after launch costs you the reviews, the ranking history and the links pointing at you.

  10. Write the changelog as you go.

    The release notes field is a required-feeling chore at submission time and a genuinely useful record afterwards. Keeping it current also stops you shipping a build whose contents you can no longer describe.