Skip to main content

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

Updates.updateId
string | null
The UUID of the current update, or null in development
const updateId = Updates.updateId;
console.log('Running update:', updateId);
Updates.channel
string | null
The release channel configured for this app
console.log('Channel:', Updates.channel); // "production"
Updates.runtimeVersion
string
The runtime version for this build
Updates.isEmergencyLaunch
boolean
Whether the app is running in emergency mode (after a fatal error)
Updates.isEmbeddedLaunch
boolean
Whether the app is running the bundled update (not downloaded OTA)

Methods

Updates.checkForUpdateAsync()
() => Promise<UpdateCheckResult>
Checks if a new update is available
const { isAvailable, manifest } = await Updates.checkForUpdateAsync();

if (isAvailable) {
  console.log('Update available!');
}
Returns:
{
  isAvailable: boolean;
  manifest?: Manifest;
}
Updates.fetchUpdateAsync()
() => Promise<UpdateFetchResult>
Downloads the most recent update
const { isNew, manifest } = await Updates.fetchUpdateAsync();

if (isNew) {
  console.log('New update downloaded');
}
Returns:
{
  isNew: boolean;
  manifest?: Manifest;
}
Updates.reloadAsync()
() => Promise<void>
Reloads the app with the most recently downloaded update
await Updates.reloadAsync();
This will immediately reload your app. Save any state before calling.
Updates.readLogEntriesAsync(maxAge)
(maxAge: number) => Promise<UpdatesLogEntry[]>
Reads expo-updates log entries
const logs = await Updates.readLogEntriesAsync(3600000); // Last hour
logs.forEach(log => console.log(log.message));
Updates.clearLogEntriesAsync()
() => Promise<void>
Clears all expo-updates log entries

Events

Updates.useUpdateEvents(listener)
(listener: (event: UpdateEvent) => void) => void
Hook to subscribe to update events
Updates.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 Support

PlatformSupported
iOS
Android
Web
Updates do not work in Expo Go. Build a development or production build to test.

Best Practices

  1. Check on Launch: Check for updates when app starts
  2. Background Downloads: Download updates in background, apply on next launch
  3. Test Updates: Test updates on staging channel before production
  4. Handle Errors: Always wrap update calls in try/catch
  5. User Choice: Let users decide when to apply updates
  6. Monitor Logs: Use log entries to debug update issues
Never call reloadAsync() without user confirmation, as it will immediately restart the app.

Resources