GuidesExpo Application ServicesAPI Reference
ArchiveExpo SnackDiscordForumsChangelog

Add expo-updates to an existing project

Learn how to add expo-updates to an existing React Native project.


The expo-updates library fetches and manages updates received from a remote server. It supports EAS Update, a hosted service that serves updates for projects using the expo-updates library.

If you are creating a new project, we recommend using npx create-react-native-app instead of npx react-native init because it will handle the following configuration for you automatically. It includes the expo-updates config plugin, which will handle the following steps for you.

Installation

The expo-updates library requires that your project already has Expo modules configured. Be sure to install it before continuing.

To get started, install expo-updates:

Terminal
- npx expo install expo-updates

Then, install pods:

Terminal
- npx pod-install

Once installation is complete, apply the changes from the following diffs to configure expo-updates in your project.

Configuration in JavaScript and JSON

You'll need to modify index.js to import expo-asset early in your app, to be able to update assets with updates.

index.js
+ import 'expo-asset';
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

Configuration for iOS

Add the Supporting directory containing Expo.plist to your project in Xcode with the following content:

Expo.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>EXUpdatesCheckOnLaunch</key>
    <string>ALWAYS</string>
    <key>EXUpdatesEnabled</key>
    <true/>
    <key>EXUpdatesLaunchWaitMs</key>
    <integer>0</integer>
    <key>EXUpdatesSDKVersion</key>
    <string>46.0.0</string>
    <key>EXUpdatesURL</key>
    <string>https://exp.host/@my-expo-username/my-app</string>
  </dict>
</plist>

Configuration for Android

Apply the following configurations to your AndroidManifest.xml file:

AndroidManifest.xml
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app" />
+ <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0" />
+ <meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{'expo-channel-name':'your-channel-name'}"/>
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_ENABLED" android:value="true" />
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0" />
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS" />

Customizing Automatic Setup for iOS

By default, expo-updates requires no additional setup. If you want to customize the installation, for example, to enable updates only in some build variants, you can instead follow these manual setup steps and then apply any customizations.

Update AppDelegate.h

apps/bare-update/ios/bareupdate/AppDelegate.h
#import <Foundation/Foundation.h>
+ #import <EXUpdates/EXUpdatesAppController.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>

#import <ExpoModulesCore/EXAppDelegateWrapper.h>

- @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate>
+ @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>

Update AppDelegate.mm

There are multiple changes to apply to your project's AppDelegate.mm.

apps/bare-update/ios/bareupdate/AppDelegate.mm
+ @interface AppDelegate () <RCTBridgeDelegate>
+
+ @property (nonatomic, strong) NSDictionary *launchOptions;
+
+ @end
+
 @implementation AppDelegate

apps/bare-update/ios/bareupdate/AppDelegate.mm
-  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
+  self.launchOptions = launchOptions;
+  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+  #ifdef DEBUG
+    [self initializeReactNativeApp];
+  #else
+    EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
+    controller.delegate = self;
+    [controller startAndShowLaunchScreen:self.window];
+  #endif
+
+  [super application:application didFinishLaunchingWithOptions:launchOptions];
+
+  return YES;
+ }
+
+ - (RCTBridge *)initializeReactNativeApp
+ {
+  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
   RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
   id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"];
   if (rootViewBackgroundColor != nil) {
apps/bare-update/ios/bareupdate/AppDelegate.mm
     rootView.backgroundColor = [UIColor whiteColor];
   }

-  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
   UIViewController *rootViewController = [UIViewController new];
   rootViewController.view = rootView;
   self.window.rootViewController = rootViewController;
   [self.window makeKeyAndVisible];

-  [super application:application didFinishLaunchingWithOptions:launchOptions];
-
-  return YES;
+  return bridge;
  }

 - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
apps/bare-update/ios/bareupdate/AppDelegate.mm
  #ifdef DEBUG
   return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  #else
-  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+  return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
  #endif
 }

+ - (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
+  appController.bridge = [self initializeReactNativeApp];
+ }

 // Linking API
 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
   return [RCTLinkingManager application:application openURL:url options:options];

Expo.plist

apps/bare-update/ios/bareupdate/Supporting/Expo.plist
     <key>EXUpdatesURL</key>
     <string>https://exp.host/@my-expo-username/my-app</string>
+    <key>EXUpdatesAutoSetup</key>
+    <false/>
   </dict>
 </plist>

Customizing Automatic Setup for Android

By default, expo-updates requires no additional setup. If you want to customize the installation, for example, to enable updates only in some build variants, you can instead follow these manual setup steps and then apply any customizations.

AndroidManifest.xml

apps/bare-update/android/app/src/main/AndroidManifest.xml
   <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
     <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app"/>
     <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0"/>
+    <meta-data android:name="expo.modules.updates.AUTO_SETUP" android:value="false"/>
     <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen">
       <intent-filter>
         <action android:name="android.intent.action.MAIN"/>

MainApplication.java

There are multiple changes to apply to your project's MainApplication.java

apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
 import android.app.Application;
 import android.content.Context;
 import android.content.res.Configuration;
+ import android.net.Uri;

 import com.facebook.react.PackageList;
 import com.facebook.react.ReactApplication;
apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
 import expo.modules.ApplicationLifecycleDispatcher;
 import expo.modules.ReactNativeHostWrapper;
+ import expo.modules.updates.UpdatesController;

 import com.facebook.react.bridge.JSIModulePackage;
 import com.swmansion.reanimated.ReanimatedJSIModulePackage;
apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
 import java.lang.reflect.InvocationTargetException;
 import java.util.Arrays;
 import java.util.List;
+ import javax.annotation.Nullable;

 public class MainApplication extends Application implements ReactApplication {
   private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
     protected JSIModulePackage getJSIModulePackage() {
       return new ReanimatedJSIModulePackage();
     }
+
+    @Override
+    protected @Nullable String getJSBundleFile() {
+      if (BuildConfig.DEBUG) {
+        return super.getJSBundleFile();
+      } else {
+        return UpdatesController.getInstance().getLaunchAssetFile();
+      }
+    }
+
+    @Override
+    protected @Nullable String getBundleAssetName() {
+      if (BuildConfig.DEBUG) {
+        return super.getBundleAssetName();
+      } else {
+        return UpdatesController.getInstance().getBundleAssetName();
+      }
+    }
   });
apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
     super.onCreate();
     SoLoader.init(this, /* native exopackage */ false);

+    if (!BuildConfig.DEBUG) {
+      UpdatesController.initialize(this);
+    }
+
     initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
     ApplicationLifecycleDispatcher.onApplicationCreate(this);
   }

Usage

See more information about usage in the expo-updates README.

FAQ

How do I customize which assets are included in an update bundle?

If you have assets (such as images or other media) that are imported in your application code, and you would like these to be downloaded atomically as part of an update, add the assetBundlePatterns field under the expo key in your project's app.json. This field should be an array of file glob strings that point to the assets you want to be bundled. For example: "assetBundlePatterns": ["**/*"]