Use appVersion policy for runtime version updates
The appVersion policy automatically increments the runtime version whenever the app version is incremented. This reduces the risk of incompatible updates, though it requires remembering to bump the app version when changing native runtime. If you want to make incompatible updates extremely unlikely at the cost of making it necessary to create builds more often, use the fingerprint policy instead.
Fingerprint policy automatically updates runtime for native changes
The fingerprint policy increments the runtime version whenever anything that may impact the native runtime changes. This makes incompatible updates extremely unlikely, but at the cost of making it necessary to create builds more often. More information about fingerprinting is available in the SDK documentation.
Manually increment runtime version when native code changes
Whenever installing or updating native code, you can manually increment the runtimeVersion property in the project's app config. This gives explicit control over runtime version updates.
EAS Update purpose
EAS Update allows you to address small bugs and push quick fixes directly to end users.
EAS Update publishes project in production mode
A published project using eas update runs in production mode.
disableAntiBrickingMeasures configuration in app.json
In app.json, configure disableAntiBrickingMeasures under the updates object. This can be configured dynamically using app.config.js. Example:
{
"expo": {
"updates": {
"disableAntiBrickingMeasures": true
}
}
}
Override request headers at runtime with setUpdateRequestHeadersOverride
You can override request headers at runtime using Updates.setUpdateRequestHeadersOverride() to change which channel updates are pulled from without creating a new build. This feature is available in Expo SDK 54 with expo-updates version 0.29.0 and later. The primary use case is channel surfing to switch between channels like 'default' for production and 'preview' for preview updates. After calling setUpdateRequestHeadersOverride, call fetchUpdateAsync() to fetch the update and reloadAsync() to reload the app, or wait for the next launch.
expo-channel-name header requires native build configuration
To override the expo-channel-name header at runtime, the native build must include it. When a build profile in eas.json defines 'channel', EAS Build sets this header automatically. Builds created outside EAS Build (such as with npx expo run:android) do not set it automatically. For those builds, declare the header in updates.requestHeaders in your app config, then rebuild the app.
Override update URL and request headers with setUpdateURLAndRequestHeadersOverride
You can use Updates.setUpdateURLAndRequestHeadersOverride() to override both the update URL and request headers at runtime, allowing you to load a specific update by ID even if the update was published before the current build was created. This feature is available in Expo SDK 52 with expo-updates version 0.27.0 and later. The method signature is: Updates.setUpdateURLAndRequestHeadersOverride({ updateUrl: string, requestHeaders: Object }).
disableAntiBrickingMeasures field in app config
The disableAntiBrickingMeasures field in the app config disables anti-bricking measures that ensure subsequent updates can always be published to fix issues in previously-installed updates. Do not enable this in production builds. When enabled, if a new update causes the app to crash, expo-updates cannot automatically recover because there will be no update to rollback to, and the user would need to uninstall and reinstall the app. This field should only be used in preview builds.
User must close and reopen app after overriding update URL
After calling setUpdateURLAndRequestHeadersOverride(), the expo-updates library will not use the new overridden URL and request headers until the app is completely closed (killed, not just backgrounded) and reopened. You should notify the user via an alert that they need to close and re-open the app to fetch and launch the new update.
New update downloads and launches on next app open
After the app is closed and reopened following a setUpdateURLAndRequestHeadersOverride() call, the update and its related assets will be downloaded. While downloading, the user will wait on the splash screen. Once assets are ready, the app will launch.
Security risks of disabling anti-bricking measures
Disabling anti-bricking measures with disableAntiBrickingMeasures creates security vulnerabilities. An employee with the ability to publish updates could publish a malicious update that changes the update URL and request headers to point to their own server and take over app installations. This risk can be mitigated, but not eliminated, by using code signing for production updates and limiting access to the key. CodePush had the same risk when allowing developers to swap deployment keys.
Override request headers example code
import * as Updates from 'expo-updates';
Updates.setUpdateRequestHeadersOverride({ 'expo-channel-name': 'preview' });
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
Override update URL and request headers example code
import * as Updates from 'expo-updates';
function overrideUpdateURLAndHeaders() {
Updates.setUpdateURLAndRequestHeadersOverride({
updateUrl: 'https://u.expo.dev/{updateId}/group/{groupId}',
requestHeaders: {},
});
alert('Close and re-open the app to load the latest version.');
}
Custom request headers must be declared in app config
Any custom request header you want to override at runtime must be declared in updates.requestHeaders in your app config. When calling setUpdateRequestHeadersOverride(), the object you pass replaces all custom request headers from the build, so you must include every header the app still needs while the override is active.
Channel surfing use case for overriding request headers
Channel surfing is a primary use case for overriding request headers at runtime. It allows you to switch the expo-channel-name request header at runtime as part of a complete channel switching workflow. For example, you can override from a 'default' channel for production updates to a 'preview' channel for preview updates, enabling you to test preview updates in your current production builds.
Risks when switching channels at runtime
Switching channels at runtime can cause compatibility issues when updates use different migrations or data shapes. See the risks and considerations when switching channels documentation for more details.
Disable bundle diffing between updates on SDK 56+
On SDK 56 and later, bundle diffing between updates is enabled by default. To disable it, set `updates.enableBsdiffPatchSupport` to `false` in app.json.
app.json configuration for bundle diffing between updates
Set the following in app.json to enable bundle diffing between updates on SDK 55: {"expo": {"updates": {"enableBsdiffPatchSupport": true}}}
Bundle diffing reduces update download size
With bundle diffing, EAS Update delivers a bundle patch when possible instead of the full bundle. When you publish a new update, EAS Update generates a smaller file containing only the differences between the bundle currently running on the device and the new bundle, which often reduces update download size significantly.
Bundle diffing SDK requirement
Your app must be on Expo SDK 55 or later to use bundle diffing for EAS Update.
Enable bundle diffing between updates on SDK 55
On SDK 55, set the configuration option `updates.enableBsdiffPatchSupport` to `true` in your project's app.json to opt in to receiving patches between updates. This feature is enabled by default starting in SDK 56.
Enable patches from embedded bundle experimental feature
Patches from the embedded bundle is an experimental opt-in feature. To enable it, set the `EAS_UPDATE_EXPERIMENTAL_UPLOAD_EMBEDDED_BUNDLE` environment variable to `1` under `env` in a build profile in eas.json. The flag and behavior may change.
eas.json configuration for embedded bundle uploads
Set the following in eas.json to enable experimental embedded bundle uploads in a production build profile: {"build": {"production": {"env": {"EAS_UPDATE_EXPERIMENTAL_UPLOAD_EMBEDDED_BUNDLE": "1"}}}}
Upload embedded bundle without EAS Build
If you don't use EAS Build, you can upload an embedded bundle yourself using the command: eas update:embedded:upload --platform [platform] --bundle [path] --manifest [path] --channel [name]. Point --bundle at the JavaScript bundle and --manifest at the app.manifest produced by the native build.
List embedded bundles command
Use the command `eas update:embedded:list` to find the ids of embedded bundles registered for your project.
View embedded bundle command
Use the command `eas update:embedded:view [id]` to view details of a single embedded bundle.
Delete embedded bundle command
Use the command `eas update:embedded:delete [id]` to delete an embedded bundle. This command is safe to retry.
Verify bundle diffs on Expo website
You can confirm that bundle diffs are being served from the Update Details page on expo.dev. Open the Update Group you published, then select the platform you want to inspect.
Verify bundle diffs with Updates API
You can confirm that bundle diffs are being served by inspecting update logs with `Updates.readLogEntriesAsync()`. If your app received a patch, you will see an entry indicating it was successfully applied.
Patch generation delay and on-demand computation
When an update is published, EAS Update precomputes a patch only against the second-newest update on the channel. If a device requests the new update while running a different published update, it will initially receive the full bundle. A patch for that specific base update is then generated on demand and served to future similar requests.
Patch ready time after publishing
Patches are generated shortly after publishing an update. It can take a few minutes between publishing an update and the patch being ready. During that window, devices may receive the full bundle.
Bundle patch generation uses bsdiff algorithm
EAS Update uses the bsdiff algorithm to generate bundle patches.
When patches are not served
A patch is not served if it is not meaningfully smaller than the full bundle, or if generating the patch is too resource intensive. In these cases, EAS Update serves the full bundle instead.
Fresh installs and bundle diffing limitation
Fresh installs receive the full bundle on their first update by default, unless you opt in to patches from the embedded bundle feature.