or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-config.mdapp-state-config.mdindex.mdlogging-debugging.mdreact-integration.mdupdate-management.md
tile.json

update-management.mddocs/

Update Management

Core update lifecycle functions for checking, downloading, and applying remote updates with full error handling, state tracking, and security verification.

Capabilities

Check for Updates

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
}

Fetch Updates

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>
  );
}

Reload App

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:

  • It is not recommended to place meaningful logic after await Updates.reloadAsync() as the promise resolves immediately before posting the reload task
  • The method cannot be used in Expo Go or development mode
  • Ensure proper error handling as the promise may reject if the JS runtime cannot be found
  • On iOS, ensure EXUpdatesAppController bridge property is set correctly
  • On Android, ensure UpdatesController.initialize or UpdatesController.setReactNativeHost is called properly