skip to content

A budgeting app already on both stores is moving to EAS builds on every merge to main; how do you switch it to remote build numbers without a rejected upload?

level: seniorimportance: should knowfreq 35%

answer

  1. seed the counter from the stores
  2. eas build:version:set
  3. autoIncrement on the production profile
  4. delete versionCode and buildNumber from config
  5. nativeVersion policy must go

basics

~20 s

Seed EAS's counter with the last build number each store accepted using eas build:version:set, set appVersionSource to remote and autoIncrement on the production profile, remove build numbers from app config, and replace a nativeVersion runtime policy.

solid answer

~40 s

The risk is the **first** remote build: EAS seeds its counter from the local project, and a stale `versionCode` in app.json would produce a number the store has already seen. So first run `eas build:version:set` for each platform, agree to switch `cli.appVersionSource` to `"remote"`, and enter the last build number each store accepted. Then set `"autoIncrement": true` on the `production` build profile, which the merge-to-main pipeline uses, and delete `android.versionCode` and `ios.buildNumber` from app config because remote ignores them. If the project uses EAS Update with the `nativeVersion` runtime policy, switch to `appVersion`, since remote rejects it. Teammates building in Xcode or Android Studio run `eas build:version:sync`, and the app reads its build number through `expo-application`.

code

bash · 6 lines
bash
# once per platform: seed the remote counter with the store's last build
eas build:version:set --platform android
eas build:version:set --platform ios

# check what EAS will start from
eas build:version:get --platform all

go deeper

for a junior

Recall that the stores reject a reused build number and that eas build:version:set tells EAS where to start counting.

for a middle

Explain why the first remote build is seeded from the local project and what autoIncrement on the production profile does from then on.

for a senior

Plan the migration end to end: seeding from store values, removing dead config, the nativeVersion conflict, off-EAS builds and diagnosing a later duplicate.

for a principal

Decide which profiles share a counter, who may build outside the pipeline, and how build numbers map to release and update policies.

## The situation The budgeting app has shipped for a year from laptops. Its app.json says `"versionCode": 38` because someone forgot to commit a bump, while Google Play has already accepted build 212 and App Store Connect build 209. The team now wants a build on **every merge to main**, and no one wants to hand-edit a number again. Under a local version source this cannot work reliably in CI (the bump is lost with the checkout), so the answer is a **remote version source** — migrated carefully. ## Why the first remote build is the dangerous one With `cli.appVersionSource: "remote"`, EAS servers store the build version per application identifier. When no value is stored yet, EAS CLI **seeds** the counter from what it reads in the local project. With `autoIncrement` on, the stale `38` would become `39`, a number far below what Google Play already holds, and the upload would be refused. If EAS CLI cannot read a version at all, it stops and tells you to run `eas build:version:set`. Either way, the fix is to seed the counter deliberately. ## The migration, step by step 1. **Find the real numbers.** Look up the highest build version each store has accepted: 212 on Android, 209 on iOS. 2. **Seed EAS.** Run `eas build:version:set`, choose the platform, answer yes to "Do you want to set app version source to remote now?", and enter the store's last value. Repeat for the other platform. The command also writes `"appVersionSource": "remote"` into `eas.json`. 3. **Turn on incrementing.** Add `"autoIncrement": true` to the `production` build profile, which is the profile the main-branch pipeline builds. The next build gets 213 on Android and 210 on iOS. 4. **Remove the dead values.** Delete `android.versionCode` and `ios.buildNumber` from app config. Remote ignores them, EAS CLI warns about them, and leaving them invites someone to "fix" the wrong number later. 5. **Check update compatibility.** If `runtimeVersion` uses `{ "policy": "nativeVersion" }`, EAS CLI refuses remote versioning; switch to `appVersion`, which Expo names as the equivalent. 6. **Keep off-EAS builds aligned.** Anyone who builds in Xcode or Android Studio first runs `eas build:version:sync` to write the stored numbers into the native project. ## What the pipeline gains | Concern | Local source in CI | Remote source | |---|---|---| | Where the counter lives | app.json in each checkout | EAS servers, one per app identifier | | Bump persistence | lost unless committed back | automatic | | Commit noise | a version-bump commit per build | none | | Laptop and CI agree | only if everyone pulls first | always | The bump happens when a build is requested, so a build that later fails still uses up its number. That leaves gaps in the sequence, which the stores accept: they require a higher number, not a contiguous one. ## Things that do not change - The **user-facing `version`** (1.8.0) is still set by hand when a release cycle begins; `autoIncrement: "version"` is rejected under remote. - Code that shows the build number to users or support should read `Application.nativeBuildVersion` from **`expo-application`**. With remote, app config no longer holds the real value, so reading it from `expo-constants` shows a stale or missing number. - Preview builds may share the counter with production if they use the same application identifier, so decide deliberately whether `preview` should increment too. ## Android and iOS are separate counters The two platforms never share a number: Android's `versionCode` and iOS's `buildNumber` are separate values, checked by separate stores. EAS stores each one against its own application identifier, which is why `eas build:version:set` is run once per platform and why the two can legitimately differ, as 213 and 210 do here. A migration that seeds only Android leaves iOS seeded from app.json on its first remote build, which is the same stale-value trap on the other store. ## Diagnosing it later If a store still rejects a duplicate after migration, run `eas build:version:get` and compare with the store. The usual causes are a build uploaded outside EAS without `eas build:version:sync` afterwards, or a platform that was never seeded.

  • Why must a nativeVersion runtime policy change before switching to a remote version source?
    `nativeVersion` derives the runtime version from the native version and build number in the project. With remote, the build number is injected at build time and is not in the project, so EAS CLI rejects the combination and suggests the `appVersion` policy.
  • A release engineer uploads a hotfix built in Xcode after the migration; what can go wrong on the next EAS build?
    If the Xcode build used a number above EAS's stored value, the next EAS build may reuse it and be rejected. Run `eas build:version:sync` before a manual build, then `eas build:version:set` if the manual upload took a number EAS did not issue.

saying these in an interview costs you the question

  • Turning on remote versioning copies the store's latest build number automatically
  • Stale versionCode in app.json is harmless because remote ignores it from day one
  • Remote versioning also bumps the user-facing version on each release
  • A failed build returns its build number to the pool
  • expo-constants is the reliable way to read the remote build number at runtime