In an Expo app config, how do the appVersion, nativeVersion and fingerprint runtimeVersion policies differ, and when would you set a plain string instead?
answer
- string vs policy object
- version, version plus build number, hash
- platform key beats root key
- bare projects: string or fingerprint
basics
~10 sappVersion copies the app version, nativeVersion adds the build number, and fingerprint hashes native-affecting project inputs. A plain string gives full manual control and, besides fingerprint, is the only option in a bare project.
solid answer
~40 s`runtimeVersion` is either a string or `{ "policy": ... }`. `appVersion` resolves to `version` (or `ios.version` / `android.version`), so all builds of one release share a runtime; it is what `eas update:configure` writes for a Continuous Native Generation project. `nativeVersion` resolves to `version(buildNumber)` or `version(versionCode)`, such as `"1.0.0(1)"`, giving each build its own runtime and often different runtimes per platform. `fingerprint` hashes native-affecting inputs with `@expo/fingerprint`, so it changes automatically when native code changes, at the cost of more builds. `ios.runtimeVersion` and `android.runtimeVersion` override the top-level value. A plain string is for full manual control, and in a bare project with committed native folders only a string or `fingerprint` works.
code
json · 11 lines{
"expo": {
"version": "2.3.0",
"runtimeVersion": { "policy": "appVersion" },
"ios": { "buildNumber": "41" },
"android": {
"versionCode": 57,
"runtimeVersion": "2.3.0-android-2"
}
}
}go deeper
Recall the three policy names, what each copies or computes, and that a plain string is the manual alternative.
Explain the exact resolved values, the per-platform precedence rule, and why appVersion and nativeVersion are unavailable in a bare project.
Choose a policy from the team's release cadence, and name each policy's failure mode: shared runtimes under appVersion, update sprawl under nativeVersion, extra builds under fingerprint.
Weigh the human discipline a policy relies on against build cost, and decide whether the organisation standardises one policy across apps or lets each team choose.
## Two ways to write `runtimeVersion` In an Expo app config the `runtimeVersion` field accepts two shapes: - **A plain string**, such as `"1.0.0"`. You own it completely: it changes only when you edit it. This gives full control over which builds share a runtime, and it is the form `eas update:configure` writes for a bare project (`"1.0.0"`). - **A policy object**, `{ "policy": "<name>" }`. The runtime version is derived from other project data each time a build is made and each time `eas update` publishes. For a project using Continuous Native Generation, `eas update:configure` writes `{ "policy": "appVersion" }`. The string `"file:fingerprint"` is reserved; setting it by hand is rejected with a message pointing you to the fingerprint policy. ## The three current policies **`appVersion`** resolves to the app's `version` field, or to `ios.version` / `android.version` when those are set for that platform (with no version at all it falls back to `"1.0.0"`). Expo's deployment guide recommends it as the default because every store release that bumps the version then has its own runtime to target. Its weak spot is that several builds of the same version share one runtime; if native code changes between them and the version does not, updates meant for one will be served to the other. **`nativeVersion`** combines the version with the build number: `version(buildNumber)` on iOS and `version(versionCode)` on Android, so version `1.0.0` with build number `1` resolves to `"1.0.0(1)"`. Every build with a new build number gets its own runtime, which makes accidental sharing unlikely but means each build needs its own updates. Because iOS build numbers and Android version codes often differ, the two platforms usually end up on separate runtime versions, and you manage those numbers yourself. **`fingerprint`** hashes the project with `@expo/fingerprint` (dependencies with native code, config plugins, native-affecting app config, and the native folders when they are committed). The runtime version changes automatically whenever something that may affect the native layer changes. The cost is more builds, since anything the hash sees as native starts a new runtime. Expo's deployment guide still recommends `appVersion` for most teams and presents fingerprint as the direction it hopes policies will take. ## Comparison | | Plain string | `appVersion` | `nativeVersion` | `fingerprint` | |---|---|---|---|---| | Source of the value | You | `version` | `version` + build number | Hash of native-affecting inputs | | Example | `"3"` | `"1.4.0"` | `"1.4.0(12)"` | a SHA-1 hex string | | Changes on native change? | Only if you edit it | Only if you bump `version` | Only if you bump a number | Yes, automatically | | Builds sharing a runtime | As many as you allow | All builds of a version | One per build number | All builds with identical native inputs | | Works in a bare project? | Yes | No | No | Yes | ## Per-platform overrides `ios.runtimeVersion` and `android.runtimeVersion` accept the same string-or-policy shapes. When both a top-level and a platform-specific value exist, **the platform-specific one wins** for that platform. Typical uses: - ship an Android-only native fix by bumping only `android.runtimeVersion`; - run `fingerprint` on one platform while the other keeps a hand-managed string. `eas update` resolves each platform separately and publishes an update group in which each platform's update carries its own runtime version. ## Bare projects When `android/` or `ios/` is committed rather than generated (`expo-updates` calls this the `generic` workflow), the `appVersion` and `nativeVersion` policies are not supported: resolution fails with an error telling you to set the runtime version as a string. Only a string or the `fingerprint` policy works there, and the native files (`Expo.plist` and `AndroidManifest.xml`) must carry the matching setting, because nothing regenerates them for you. ## A legacy name you may still meet The `sdkVersion` policy derived the runtime from the Expo SDK version and was the default for SDK 48 and earlier. The config types still accept it, but the current `expo-updates` documentation lists only the three policies above. ## How to choose 1. Start from how often native code changes relative to store releases. 2. If native changes only happen with a version bump, `appVersion` is simple and readable. 3. If native changes happen between builds of the same version, use `nativeVersion` or `fingerprint`. 4. If you cannot trust people to remember a bump, `fingerprint` removes the memory step at the price of more builds.
- What happens if a project with committed android and ios folders uses the appVersion policy?`expo-updates` treats that platform as a bare (`generic`) project, and resolving `appVersion` or `nativeVersion` fails with an error telling you to set the runtime version manually as a string. In that setup you use a string, kept in step with `Expo.plist` and `AndroidManifest.xml`, or the `fingerprint` policy, which then hashes the committed native folders too.
- Why does nativeVersion often give iOS and Android different runtime versions?It embeds the platform's build number: `ios.buildNumber` on iOS and `android.versionCode` on Android. Those counters usually drift apart, so version 1.2.0 might resolve to `"1.2.0(7)"` on iOS and `"1.2.0(9)"` on Android. That is fine, because `eas update` stamps each platform's update with its own runtime version, but it means every new build number needs matching updates.
- When would you use a per-platform runtimeVersion override?When one platform's native layer changes and the other's does not, for example an Android-only native fix. Bumping `android.runtimeVersion` starts a new Android runtime while iOS builds keep receiving updates on their existing runtime. The platform-specific value always wins over the top-level one for that platform.
saying these in an interview costs you the question
- The appVersion policy includes the build number in the runtime version.
- A top-level runtimeVersion overrides ios.runtimeVersion and android.runtimeVersion.
- Any runtime version policy works the same in a bare project with committed native folders.
- The fingerprint policy hashes the app's JavaScript source, so every JS change needs a build.
- nativeVersion always gives iOS and Android the same runtime version.