HomeGuidesReferenceLearn
ArchiveExpo SnackDiscord and ForumsNewsletter

Runtime versions and updates

Learn about different runtime version policies and how they may suit your project.


Runtime versions are a property that guarantees compatibility between a build's native code and an update. When a project is made into a build, the build will contain some native code that cannot be changed with an update. Therefore, an update must be compatible with a build's native code to run on the build.

To illustrate how builds and updates interact, take a look at the following diagram:

Builds can be thought of as two layers: a native layer that's built into the app's binary, and an update layer, that is swappable with other compatible updates. This separation allows us to ship bug fixes to builds as long as the update with the bug fix can run on the native layer inside the build. The "runtimeVersion" property allows us to guarantee that an update is compatible with a specific build's native code.

Since updates must be compatible with a build's native code, any time native code is updated, we're required to make a new build before publishing an update. Some developers only update native code when upgrading to a new Expo SDK, while others may upgrade native code between builds or at other intervals. Below is an explanation of different situations and configurations that may suite your project.

Setting "runtimeVersion"

To make managing the "runtimeVersion" property easier between builds and updates, we've created runtime version policies that will update automatically based on other fields inside the app config (app.json/app.config.js). If these policies do not match the development flow of a project, there's also an option to set the "runtimeVersion" manually.

"appVersion" runtime version policy (available in SDK 46 and higher)

The "appVersion" runtime is provided for projects with custom native code that may change between builds. By default, the runtime version policy is set to "appVersion" after running eas update:configure:

{
  "expo": {
    "runtimeVersion": {
      "policy": "appVersion"
    }
  }
}

For a project that has the following in its app config:

{
  "expo": {
    "runtimeVersion": {
      "policy": "appVersion"
    },
    "version": "1.0.0",
    "ios": {
      "buildNumber": "1"
    },
    "android": {
      "versionCode": 1
    }
  }
}

The "appVersion" policy will set the runtime version to the project's current "version" property. The runtime version for the Android and iOS builds and any updates would be in this case "1.0.0".

This policy is great for projects that contain custom native code and that update the "version" field after every public release. To submit an app, the app stores require an updated native version number for each submitted build, which makes this policy convenient if you want to be sure that every version installed on user devices has a different runtime version.

When using this policy, you need to manually update "version" field in app config every time there is a public release, but for Play Store's Internal Test Track and the App Store's TestFlight uploads, you can rely on "autoIncrement" option in eas.json to manage versions for you.

"nativeVersion" runtime version policy

We provide the "nativeVersion" runtime version policy for projects that have custom native code that may change between builds:

{
  "expo": {
    "runtimeVersion": {
      "policy": "nativeVersion"
    }
  }
}

The "nativeVersion" policy will set the runtime version to the project's current "version" and "buildNumber" (iOS) or "versionCode" (Android) properties. For a project that has the following in its app config:

{
  "expo": {
    "runtimeVersion": {
      "policy": "nativeVersion"
    },
    "version": "1.0.0",
    "ios": {
      "buildNumber": "1"
    },
    "android": {
      "versionCode": 1
    }
  }
}

The runtime version for the Android and iOS builds and any updates would be the combination of "[version]([buildNumber|versionCode])", which in this case would be "1.0.0(1)".

This policy is great for projects that contain custom native code and that update the native version numbers ("buildNumber" for iOS and "versionCode" for Android) for each build. To submit an app, the app stores require an updated native version number for each submitted build, which makes this policy convenient if you want to be sure that every app uploaded to the Play Store's Internal Test Track and the App Store's TestFlight distribution tools has a different runtimeVersion.

It's important to know that this policy does require the developer to manage the native version numbers manually between each build.

Also, if you select a different native version between Android and iOS, you'll end up with builds and updates with separate runtime versions. When you publish an update, EAS CLI will detect that two updates are needed, and it will create two updates with the required runtime versions and publish them separately.

"fingerprint" runtime version policy

The fingerprint runtime policy is still in beta as of SDK 51 and not yet considered stable. Use may result in unexpected EAS Update behavior.

We provide the "fingerprint" runtime version policy for Expo projects that automatically calculates the runtime version for you, including through changes like SDK upgrades or adding custom native code.

{
  "expo": {
    "runtimeVersion": {
      "policy": "fingerprint"
    }
  }
}

This policy works for both projects with and without custom native code. It works by using the @expo/fingerprint package to calculate the hash of your project during builds and updates to determine build-update compatibility (also known as the runtime).

"fingerprintExperimental" runtime version policy

fingerprintExperimentalis deprecated and is removed from SDK 51 onwards.

We provide the "fingerprintExperimental" runtime version policy for a project that has custom native code that may change between builds:

{
  "expo": {
    "runtimeVersion": {
      "policy": "fingerprintExperimental"
    }
  }
}

This policy is well-suited for projects incorporating custom native code that require safe updates without native conflicts. Unlike the "nativeVersion" policy, the "fingerprintExperimental" option automatically scans your project's native dependencies to update the "runtimeVersion" accordingly.

Enabling fingerprinting may lengthen build times, as the fingerprinting process can be resource-intensive.

"sdkVersion" runtime version policy (deprecated)

Deprecated: The "sdkVersion" runtime version policy is deprecated, and the "appVersion" policy is recommended instead. This policy should only be used for testing updates with Expo Go on SDK 48 or lower.

The "sdkVersion" policy will set the runtime version to the current SDK version of a project. For instance, if the project is on Expo SDK 1.0.0, the runtime version with this policy will be "exposdk:1.0.0". This runtime version will update any time we update the project's Expo SDK. So, if we ran expo upgrade and installed Expo SDK 2.0.0, then the runtime version would become "exposdk:2.0.0".

{
  "expo": {
    "runtimeVersion": {
      "policy": "sdkVersion"
    }
  }
}

Custom "runtimeVersion"

You can also set a custom runtime version that meets the runtime version formatting requirements:

{
  "expo": {
    "runtimeVersion": "1.0.0"
  }
}

This option is good for developers who want to manage the runtime version manually, separately from any other version numbers present in a project's app config. It gives the developer complete control over which updates are compatible with which builds.

Platform specific "runtimeVersion"

You can also set a runtime version for a specific platform:

{
  "expo": {
    "android": {
      "runtimeVersion": "1.0.0"
    }
  }
}

When both a top level runtime and a platform specific runtime are set, the platform specific one takes precedence.

Avoiding crashes with incompatible updates

The main issue that can arise when publishing updates is that the update could rely on native code that the build it's running on does not support. For instance, imagine we made a build with a runtime version of "1.0.0". Then, we submitted that build to the app stores and released it to the public.

Later on, imagine that we developed an update that relied on a newly installed native library, like the expo-in-app-purchases library, and we did not update the "runtimeVersion" property, so that it is still "1.0.0". If we published an update, the builds with the "runtimeVersion" of "1.0.0" would think the incoming update with the same runtime version was compatible and it would attempt to load the update. Since the update would make calls to code that does not exist inside the build, the app would crash.

There are a few ways to avoid crashes like this:

  • Whenever we install or update native code, iterate the "runtimeVersion" property in the project's app config (app.json/app.config.js).
  • Create a preview build of your app and load it on a test device. Then, publish the update to the "preview" branch to make sure it works as expected before publishing it to the project's production branch.

If this error does occur, then you can republish a previous known-good update, then ask users to delete the app and reinstall it.

In the future, the expo-updates library will prevent many instances of this from crashing the app. If we can detect this particular issue, we'll automatically roll back to a previous update instead of loading the bad update.