Core update lifecycle functions for checking, downloading, and applying remote updates with full error handling, state tracking, and security verification.
Checks the server to see if a newly deployed update is available without downloading it. Uses device bandwidth judiciously and respects rate limiting.
/**
* Checks the server to see if a newly deployed update to your project is available.
* Does not actually download the update. Cannot be used in development mode.
* @returns Promise that fulfills with an UpdateCheckResult object
* @throws CodedError if in development mode or expo-updates is not enabled
*/
function checkForUpdateAsync(): Promise<UpdateCheckResult>;
type UpdateCheckResult = UpdateCheckResultRollBack | UpdateCheckResultAvailable | UpdateCheckResultNotAvailable;
interface UpdateCheckResultAvailable {
/** Whether an update is available */
isAvailable: true;
/** The manifest of the update when available */
manifest: Manifest;
/** Whether a roll back to embedded update is available */
isRollBackToEmbedded: false;
/** If no new update is found, this contains the reason */
reason: undefined;
}
interface UpdateCheckResultNotAvailable {
isAvailable: false;
manifest: undefined;
isRollBackToEmbedded: false;
reason: UpdateCheckResultNotAvailableReason;
}
interface UpdateCheckResultRollBack {
isAvailable: false;
manifest: undefined;
isRollBackToEmbedded: true;
reason: undefined;
}
enum UpdateCheckResultNotAvailableReason {
/** No update manifest or rollback directive received from the update server */
NO_UPDATE_AVAILABLE_ON_SERVER = 'noUpdateAvailableOnServer',
/** Update manifest received but update is not launchable or doesn't pass selection policy */
UPDATE_REJECTED_BY_SELECTION_POLICY = 'updateRejectedBySelectionPolicy',
/** Update has been previously launched on this device and never successfully launched */
UPDATE_PREVIOUSLY_FAILED = 'updatePreviouslyFailed',
/** Rollback directive received but doesn't pass the configured selection policy */
ROLLBACK_REJECTED_BY_SELECTION_POLICY = 'rollbackRejectedBySelectionPolicy',
/** Rollback directive received but this app has no embedded update */
ROLLBACK_NO_EMBEDDED = 'rollbackNoEmbeddedConfiguration'
}Usage Examples:
import * as Updates from "expo-updates";
// Basic update check
const checkResult = await Updates.checkForUpdateAsync();
if (checkResult.isAvailable) {
console.log("New update available:", checkResult.manifest.id);
// Proceed to download
} else if (checkResult.isRollBackToEmbedded) {
console.log("Rollback to embedded update available");
} else {
console.log("No update available:", checkResult.reason);
}
// Error handling
try {
const result = await Updates.checkForUpdateAsync();
// Handle result
} catch (error) {
if (error.code === 'ERR_UPDATES_DISABLED') {
console.log("Updates disabled in development mode");
}
// Handle other errors
}Downloads the most recently deployed update to local storage. Must call reloadAsync() after to apply the update, or it will be applied on next cold start.
/**
* Downloads the most recently deployed update to your project from server to local storage.
* Cannot be used in development mode. reloadAsync() can be called after to reload the app
* using the most recently downloaded version.
* @returns Promise that fulfills with an UpdateFetchResult object
* @throws CodedError if in development mode or expo-updates is not enabled
*/
function fetchUpdateAsync(): Promise<UpdateFetchResult>;
type UpdateFetchResult = UpdateFetchResultSuccess | UpdateFetchResultFailure | UpdateFetchResultRollBackToEmbedded;
interface UpdateFetchResultSuccess {
/** Whether the fetched update is new (different version than what's currently running) */
isNew: true;
/** The manifest of the fetched update */
manifest: Manifest;
/** Whether the fetched update is a roll back to the embedded update */
isRollBackToEmbedded: false;
}
interface UpdateFetchResultFailure {
isNew: false;
manifest: undefined;
isRollBackToEmbedded: false;
}
interface UpdateFetchResultRollBackToEmbedded {
isNew: false;
manifest: undefined;
isRollBackToEmbedded: true;
}Usage Examples:
import * as Updates from "expo-updates";
// Check and fetch update
const checkResult = await Updates.checkForUpdateAsync();
if (checkResult.isAvailable) {
const fetchResult = await Updates.fetchUpdateAsync();
if (fetchResult.isNew) {
console.log("Downloaded new update:", fetchResult.manifest.id);
// Update ready to apply
await Updates.reloadAsync();
}
} else if (checkResult.isRollBackToEmbedded) {
const fetchResult = await Updates.fetchUpdateAsync();
if (fetchResult.isRollBackToEmbedded) {
console.log("Rolling back to embedded update");
await Updates.reloadAsync();
}
}
// Download with progress tracking (using useUpdates hook)
import { useUpdates } from "expo-updates";
function UpdateDownloader() {
const { isDownloading, downloadProgress } = useUpdates();
const handleDownload = async () => {
try {
const result = await Updates.fetchUpdateAsync();
if (result.isNew) {
await Updates.reloadAsync();
}
} catch (error) {
console.error("Download failed:", error);
}
};
return (
<View>
<Button title="Download Update" onPress={handleDownload} />
{isDownloading && (
<ProgressBar progress={downloadProgress || 0} />
)}
</View>
);
}Instructs the app to reload using the most recently downloaded version. Changes the loaded JavaScript bundle to the most recent update.
/**
* Instructs the app to reload using the most recently downloaded version.
* This not only reloads the app but also changes the loaded JavaScript bundle
* to that of the most recently downloaded update.
* Cannot be used in development mode.
* @param options Optional configuration for reload screen appearance
* @returns Promise that fulfills right before the reload instruction is sent
* @throws CodedError if in development mode or expo-updates is not enabled
*/
function reloadAsync(options?: {
reloadScreenOptions?: ReloadScreenOptions
}): Promise<void>;
interface ReloadScreenOptions {
/** Background color for the reload screen. Default: '#ffffff' */
backgroundColor?: string;
/** Custom image to display on the reload screen */
image?: string | number | ReloadScreenImageSource;
/** How to resize the custom image to fit the screen. Default: 'contain' */
imageResizeMode?: 'contain' | 'cover' | 'center' | 'stretch';
/** Whether to display the image at the full screen size. Default: false */
imageFullScreen?: boolean;
/** Whether to fade out the reload screen when hiding. Default: false */
fade?: boolean;
/** Configuration for the loading spinner */
spinner?: {
/** Whether to show the loading spinner */
enabled?: boolean;
/** Color of the loading spinner */
color?: string;
/** Size of the loading spinner. Default: 'medium' */
size?: 'small' | 'medium' | 'large';
};
}
interface ReloadScreenImageSource {
/** URL to the image */
url?: string;
/** Width of the image in pixels */
width?: number;
/** Height of the image in pixels */
height?: number;
/** Scale factor of the image */
scale?: number;
}Usage Examples:
import * as Updates from "expo-updates";
// Basic reload
await Updates.reloadAsync();
// Reload with custom reload screen
await Updates.reloadAsync({
reloadScreenOptions: {
backgroundColor: '#1a1a1a',
spinner: {
color: '#ffffff',
size: 'large'
}
}
});
// Reload with custom image
await Updates.reloadAsync({
reloadScreenOptions: {
backgroundColor: '#ffffff',
image: require('./assets/loading.png'),
imageResizeMode: 'contain',
fade: true
}
});
// Reload with image URL
await Updates.reloadAsync({
reloadScreenOptions: {
image: {
url: 'https://example.com/logo.png',
width: 200,
height: 200
},
imageFullScreen: false
}
});
// Complete update flow
async function performUpdate() {
try {
const checkResult = await Updates.checkForUpdateAsync();
if (checkResult.isAvailable) {
const fetchResult = await Updates.fetchUpdateAsync();
if (fetchResult.isNew) {
// Show custom reload screen while updating
await Updates.reloadAsync({
reloadScreenOptions: {
backgroundColor: '#0066cc',
spinner: {
color: '#ffffff',
enabled: true
}
}
});
}
}
} catch (error) {
console.error("Update failed:", error);
}
}Important Notes:
await Updates.reloadAsync() as the promise resolves immediately before posting the reload taskEXUpdatesAppController bridge property is set correctlyUpdatesController.initialize or UpdatesController.setReactNativeHost is called properly