App Store

    App Version Number vs Build Number (iOS & Android)

    Xcode and Android Studio side by side showing version and build number fields for an app release.

    The version number is the public, user-facing release name of your app, like 1.2.0, while the build number is an internal counter that identifies each upload of that version. On iOS they are CFBundleShortVersionString for the version and CFBundleVersion for the build; on Android they are versionName for the version and versionCode for the build. Users see the version number; the stores use the build number to tell uploads apart. You raise the version for a new release and the build for every upload.

    Short answer

    The version number is what users see, such as 1.2.0, and the build number is the internal identifier the stores use to order and distinguish uploads. On iOS, the CFBundleShortVersionString is the version and CFBundleVersion is the build. On Android, versionName is the version and versionCode is the build. The rule of thumb: raise the version number for a release users should notice, and raise the build number for every single upload, including re-uploads. Semantic versioning is a recommended convention, not a store requirement.

    What is the version number?

    The version number is the human-readable release name of your app, the one shown on its store listing, typically written as three dot-separated integers like 2.4.1. It communicates to users, and to your own team, which release they are looking at. A change here signals a meaningful iteration: new features, fixes, or changes worth a new public version.

    Because it is user-facing, the version number usually follows a convention people can read at a glance, most often semantic versioning. It is not a unique per-upload identifier; you can ship several internal builds that all carry the same version number while you test, changing only the build number between them.

    What is the build number?

    The build number identifies a single iteration of a given version, and it changes with every upload. Where the version number might stay at 1.2.0 across a week of testing, the build number increments each time you send a new binary, so 1.2.0 might have builds 1, 2, and 3 in sequence. Its job is to make each upload distinct.

    This is the number the stores lean on to tell uploads apart and to prevent confusion between two binaries that share a version. It is largely a bookkeeping value: users rarely see it, but your release process depends on it being unique and increasing, because a duplicate or lower build number is one of the most common upload rejections. Most teams automate this, incrementing the build number in their build script or CI pipeline so no one has to remember to change it by hand before every upload.

    iOS: CFBundleShortVersionString vs CFBundleVersion

    On iOS, the version number is CFBundleShortVersionString and the build number is CFBundleVersion, both keys in your Info.plist. Apple describes CFBundleShortVersionString as the release version, a string of three period-separated integers for major, minor, and maintenance revisions. It is what appears on the App Store.

    CFBundleVersion is the build version that identifies an iteration, released or unreleased. Within a single version, every build you upload to App Store Connect must have a unique, higher CFBundleVersion, which is why TestFlight lets you push build after build under the same public version. For a new App Store release, you raise CFBundleShortVersionString; for each upload, you raise CFBundleVersion.

    Android: versionName vs versionCode

    On Android, the version number is versionName and the build number is versionCode, both set in your Gradle build configuration. Google's documentation describes versionName as the string shown to users, which can be any format, though a major.minor.point string is typical. It is the only version value users see.

    versionCode is a positive integer that Android and Google Play use internally to decide which build is newer, and it must increase with every release. Google Play rejects an upload whose versionCode is not higher than the last, and it uses the value to prevent downgrades. The greatest value Google Play allows is 2,100,000,000, which is far more headroom than any app will ever need.

    iOS vs Android at a glance

    The two platforms use different names for the same two ideas, plus a few different rules. The table below lines them up so the mapping is clear.

    AspectiOS (Apple)Android (Google Play)
    User-facing versionCFBundleShortVersionString, e.g. 1.2.0versionName, e.g. 1.2.0
    Internal identifierCFBundleVersion (build number)versionCode (integer)
    Update-ordering keyVersion string, then build numberversionCode only
    Increase ruleHigher version per release, unique higher build per uploadversionCode strictly higher each upload
    FormatUp to three dot-separated integersversionName any string, versionCode integer up to 2,100,000,000
    Shown to usersVersion stringversionName

    The key mental model is that each platform has one user-facing string and one internal identifier. Learn which is which, and the platform-specific rules, such as Android's strict integer increase, fall into place. If you ship the same app to both stores, it is common to keep the user-facing version number aligned across platforms while letting each platform manage its own build identifier, so a 1.2.0 on iOS and a 1.2.0 on Android feel like the same release to users even though their internal numbers differ.

    Do you need semantic versioning?

    Semantic versioning, the MAJOR.MINOR.PATCH scheme defined at semver.org, is a widely used convention, but neither the App Store nor Google Play requires it. You are free to version your app 1.0, 2026.06, or any scheme your team agrees on, as long as you meet each store's increase rules.

    That said, semantic versioning is worth adopting because it communicates intent. A bump from 1.4.2 to 2.0.0 tells everyone this is a major release, while 1.4.2 to 1.4.3 signals a small fix. For libraries and APIs it is close to essential; for a consumer app it is a helpful, low-cost habit rather than a mandate.

    Store requirements: what must increase, and when

    The stores care about different things, so the requirements are not symmetric. On the App Store, a new public release needs a higher CFBundleShortVersionString than the last released version, and every build uploaded under a version needs a unique, higher CFBundleVersion. You can reuse a version string only while it has not been released; once a version is live, the next release must be higher.

    On Google Play, the hard rule is on versionCode: it must be a strictly higher integer than any build you have uploaded before, or the upload is rejected. versionName has no uniqueness requirement at all, so two uploads can share a versionName as long as their versionCode differs. In both stores, the safe habit is the same: always increase the build number for every upload, and increase the version number when the release is meant to be visible to users.

    Practical workflow checklist

    Most versioning mistakes come from forgetting which number to change in a given situation. The table below covers the common cases so you do not have to guess.

    SituationVersion numberBuild number
    New test build, same releaseKeep the sameIncrease
    New public store releaseIncreaseIncrease
    Hotfix or patch releaseBump the patch digitIncrease
    Re-upload after a rejected buildKeep the sameIncrease

    The single habit that prevents the most rejections is to increment the build number on every upload without exception, including a re-upload of a build you just rejected. Treat the version number as a deliberate decision and the build number as automatic.

    What version numbers do not tell you

    Version discipline keeps your releases orderly, but a version or build number says nothing about whether the binary behind it is safe. It is easy to ship build after build, each with a correctly incremented number, that all carry the same embedded API key, the same insecure network setting, or the same debuggable flag. The bookkeeping is perfect and the security is not.

    A scanner like PTKD.com analyzes your .ipa or .apk and returns findings ordered by severity and mapped to OWASP MASVS, so each build you version and upload is also checked for secrets, insecure storage, and network misconfigurations. To be clear about the boundary: PTKD does not manage or set your version and build numbers, and it does not replace your release tooling. It adds the security check that version numbers, by design, do not provide.

    What to take away

    • The version number is the user-facing release name; the build number is the internal per-upload identifier.
    • On iOS they are CFBundleShortVersionString and CFBundleVersion; on Android they are versionName and versionCode.
    • Increase the build number for every upload; increase the version number for a release users should notice.
    • Semantic versioning is a helpful convention, not a store requirement, and Android's versionCode must be a strictly higher integer each time.
    • Numbering keeps releases orderly but not secure, so scan each build with PTKD.com before you ship.
    • #version number
    • #build number
    • #cfbundleversion
    • #versioncode
    • #release engineering

    Frequently asked questions

    What is the difference between version number and build number?
    The version number is the public release name users see, like 1.2.0, and it signals a meaningful iteration. The build number is an internal identifier that changes with every upload, so several builds can share one version during testing. On iOS they are CFBundleShortVersionString and CFBundleVersion; on Android, versionName and versionCode.
    Do I need to use semantic versioning?
    No. Neither the App Store nor Google Play requires semantic versioning; you can use any scheme that meets each store's increase rules. That said, MAJOR.MINOR.PATCH is a useful convention because it communicates the size of a release at a glance, and it is close to essential for libraries and public APIs.
    What are the store requirements for version and build numbers?
    On the App Store, a new release needs a higher version string, and each build under a version needs a unique, higher build number. On Google Play, versionCode must be a strictly higher integer than any previous upload, while versionName has no uniqueness requirement. The safe habit is to increase the build number on every upload.
    Can two builds share the same version number?
    Yes. Multiple builds can carry the same version number as long as their build numbers differ, which is exactly how TestFlight and Play internal testing work: you push several builds under one version while testing. Users see the shared version number, and the store distinguishes the builds by their build number or versionCode.
    What is the Android equivalent of iOS's build number?
    versionCode. It plays the same role as iOS's CFBundleVersion, an internal identifier that must increase with each upload, but it is stricter: versionCode must be a positive integer that is strictly higher than the last, and Google Play uses it to order updates and block downgrades. iOS's build number can be a dotted string rather than a single integer.
    Does the version number affect my app's security?
    No. A version or build number is bookkeeping; it says nothing about whether the binary is secure. You can ship correctly numbered builds that all contain the same embedded secret or insecure setting. Run a security scan, such as PTKD.com (https://ptkd.com), on each build so your release process checks security as well as numbering.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free