expo-updates
Version: 55.0.8
Fetches and manages remotely-hosted assets and updates to your app’s JS bundle. Enables over-the-air (OTA) updates so you can ship bug fixes and features without waiting for app store approval.
Installation
npx expo install expo-updates
Usage
import * as Updates from 'expo-updates';
// Check for updates
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
API Reference
Properties
The UUID of the current update, or null in developmentconst updateId = Updates.updateId;
console.log('Running update:', updateId);
The release channel configured for this appconsole.log('Channel:', Updates.channel); // "production"
The runtime version for this build
Updates.isEmergencyLaunch
Whether the app is running in emergency mode (after a fatal error)
Whether the app is running the bundled update (not downloaded OTA)
Methods
Updates.checkForUpdateAsync()
() => Promise<UpdateCheckResult>
Checks if a new update is availableconst { isAvailable, manifest } = await Updates.checkForUpdateAsync();
if (isAvailable) {
console.log('Update available!');
}
Returns:{
isAvailable: boolean;
manifest?: Manifest;
}
Updates.fetchUpdateAsync()
() => Promise<UpdateFetchResult>
Downloads the most recent updateconst { isNew, manifest } = await Updates.fetchUpdateAsync();
if (isNew) {
console.log('New update downloaded');
}
Returns:{
isNew: boolean;
manifest?: Manifest;
}
Reloads the app with the most recently downloaded updateawait Updates.reloadAsync();
This will immediately reload your app. Save any state before calling.
Updates.readLogEntriesAsync(maxAge)
(maxAge: number) => Promise<UpdatesLogEntry[]>
Reads expo-updates log entriesconst logs = await Updates.readLogEntriesAsync(3600000); // Last hour
logs.forEach(log => console.log(log.message));
Updates.clearLogEntriesAsync()
Clears all expo-updates log entries
Events
Updates.useUpdateEvents(listener)
(listener: (event: UpdateEvent) => void) => void
Hook to subscribe to update eventsUpdates.useUpdateEvents((event) => {
if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
console.log('Update available:', event.manifest);
}
});
Examples
Check and Install Update
import * as Updates from 'expo-updates';
import { Alert } from 'react-native';
async function checkForUpdates() {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
Alert.alert(
'Update Available',
'A new version has been downloaded. Restart the app to use it.',
[
{ text: 'Later', style: 'cancel' },
{
text: 'Restart',
onPress: () => Updates.reloadAsync()
}
]
);
}
} catch (error) {
console.error('Error checking for updates:', error);
}
}
Auto-Update on App Start
import * as Updates from 'expo-updates';
import { useEffect } from 'react';
function App() {
useEffect(() => {
async function updateApp() {
if (__DEV__) return; // Skip in development
try {
const { isAvailable } = await Updates.checkForUpdateAsync();
if (isAvailable) {
const { isNew } = await Updates.fetchUpdateAsync();
if (isNew) {
await Updates.reloadAsync();
}
}
} catch (error) {
console.error('Update error:', error);
}
}
updateApp();
}, []);
return <YourApp />;
}
Update with Progress Indicator
import * as Updates from 'expo-updates';
import { useState } from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
function UpdateManager() {
const [checking, setChecking] = useState(false);
const [downloading, setDownloading] = useState(false);
async function handleUpdate() {
try {
setChecking(true);
const { isAvailable } = await Updates.checkForUpdateAsync();
setChecking(false);
if (!isAvailable) {
alert('No updates available');
return;
}
setDownloading(true);
await Updates.fetchUpdateAsync();
setDownloading(false);
alert('Update ready! Restarting...');
await Updates.reloadAsync();
} catch (error) {
console.error(error);
setChecking(false);
setDownloading(false);
}
}
return (
<View>
{checking && (
<>
<ActivityIndicator />
<Text>Checking for updates...</Text>
</>
)}
{downloading && (
<>
<ActivityIndicator />
<Text>Downloading update...</Text>
</>
)}
<Button
title="Check for Updates"
onPress={handleUpdate}
disabled={checking || downloading}
/>
</View>
);
}
Listen to Update Events
import * as Updates from 'expo-updates';
import { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
function UpdateStatus() {
const [status, setStatus] = useState('Checking for updates...');
Updates.useUpdateEvents((event) => {
if (event.type === Updates.UpdateEventType.ERROR) {
setStatus(`Error: ${event.message}`);
} else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) {
setStatus('App is up to date');
} else if (event.type === Updates.UpdateEventType.UPDATE_AVAILABLE) {
setStatus('Update available! Downloading...');
}
});
return (
<View>
<Text>{status}</Text>
</View>
);
}
Display Update Info
import * as Updates from 'expo-updates';
import { Text, View } from 'react-native';
function UpdateInfo() {
return (
<View>
<Text>Update ID: {Updates.updateId || 'Development'}</Text>
<Text>Channel: {Updates.channel || 'default'}</Text>
<Text>Runtime Version: {Updates.runtimeVersion}</Text>
<Text>Embedded Launch: {Updates.isEmbeddedLaunch ? 'Yes' : 'No'}</Text>
<Text>Emergency Launch: {Updates.isEmergencyLaunch ? 'Yes' : 'No'}</Text>
</View>
);
}
Staged Rollout
import * as Updates from 'expo-updates';
import * as Device from 'expo-device';
async function checkForStagedUpdate() {
// Only update 20% of users
const userId = await Device.getDeviceIdAsync();
const hash = hashCode(userId);
const shouldUpdate = Math.abs(hash) % 100 < 20;
if (!shouldUpdate) {
console.log('Not in rollout group');
return;
}
const { isAvailable } = await Updates.checkForUpdateAsync();
if (isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
}
function hashCode(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return hash;
}
Debug Logs
import * as Updates from 'expo-updates';
async function debugUpdates() {
const logs = await Updates.readLogEntriesAsync(3600000); // Last hour
console.log('Recent update logs:');
logs.forEach(log => {
console.log(`[${new Date(log.timestamp)}] ${log.message}`);
});
// Clear old logs
await Updates.clearLogEntriesAsync();
}
Configuration
app.json:
{
"expo": {
"updates": {
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0,
"url": "https://u.expo.dev/your-project-id"
},
"runtimeVersion": {
"policy": "appVersion"
}
}
}
Check Automatically Options
ON_LOAD: Check on app load (default)
ON_ERROR_RECOVERY: Only check after error
WIFI_ONLY: Only check on Wi-Fi
NEVER: Manual checks only
EAS Update
Publish updates with EAS:
# Publish to production channel
eas update --branch production --message "Bug fixes"
# Publish to staging
eas update --branch staging --message "New features"
TypeScript
import * as Updates from 'expo-updates';
import type {
UpdateCheckResult,
UpdateFetchResult,
UpdateEvent
} from 'expo-updates';
const updateId: string | null = Updates.updateId;
const channel: string | null = Updates.channel;
const checkResult: UpdateCheckResult = await Updates.checkForUpdateAsync();
const fetchResult: UpdateFetchResult = await Updates.fetchUpdateAsync();
| Platform | Supported |
|---|
| iOS | ✅ |
| Android | ✅ |
| Web | ❌ |
Updates do not work in Expo Go. Build a development or production build to test.
Best Practices
- Check on Launch: Check for updates when app starts
- Background Downloads: Download updates in background, apply on next launch
- Test Updates: Test updates on staging channel before production
- Handle Errors: Always wrap update calls in try/catch
- User Choice: Let users decide when to apply updates
- Monitor Logs: Use log entries to debug update issues
Never call reloadAsync() without user confirmation, as it will immediately restart the app.
Resources