Hosting Updates on Your Servers
For simplicity, the rest of this article will refer to hosting an update for the Android platform, but you could swap out Android for iOS at any point and everything would still be true.
First, you’ll need to export all the static files of your update so they can be served from your CDN. To do this, run
expo export --public-url <server-endpoint> in your project directory and it will output all your app’s static files to a directory named
dist. In this guide, we will use
https://expo.github.io/self-hosting-example as our example server endpoint. Asset and bundle files are named by the MD5 hash of their content. Your output directory should look something like this now:
│ └── 1eccbc4c41d49fd81840aef3eaabe862
Once you've exported your update's static files, you can host the contents on your own server. For example, in your
output directory, an easy way to host your own files is to push the contents to Github. You can enable Github Pages
to make your app available at a base URL like https://username.github.io/project-name
. To host your files on Github, you'd do something like this:
# run this from your project directory
expo export --public-url https://expo.github.io/self-hosting-example
# commit output directory contents to your repo
git init && git remote add origin firstname.lastname@example.org:expo/self-hosting-example.git
git add * && git commit -m "Update my app with this JS bundle"
git push origin master
To setup a QR code to view your hosted update, or if you want to host your files locally, follow the instructions below in the 'Loading QR Code/URL in Development' section.
On some hosting services such as AWS
, you'll need to explicitly set the header
so that OTA Updates
work correctly. Otherwise Updates.checkForUpdateAsync()
will fail with the error "Failed to fetch new update"
Here's an example of
configuration, with a deploy target
+ "headers": [
+ "source": "**/*.js",
+ "headers": [
+ "key": "Content-Type",
# export your app locally
expo export --public-url https://my-app-native.firebaseapp.com/
# deploy the app to firebase
firebase deploy --only hosting:native -m "Deploy my app"`
In order to configure your standalone binary to pull OTA updates from your server, you’ll need to define the URL where you will host your
index.json file. Pass the URL to your hosted
index.json file to the
expo build command.
You can also load an update hosted on your own servers as a QR code/URL into the Expo mobile client for development purposes.
The URI you’ll use to convert to QR code will be deeplinked using the
exp protocol. Both
exp deeplink into the mobile app and perform a request using HTTPS and HTTP respectively. You can create your own QR code using an online QR code generator from the input URI.
expo export in dev mode and then start a simple HTTP server in your output directory:
# Find your local IP address with `ipconfig getifaddr en0`
# export static app files
expo export --public-url http://`ipconfig getifaddr en0`:8000 --dev
# cd into your output directory
# run a simple http server from output directory
python -m SimpleHTTPServer 8000
exp://192.xxx.xxx.xxx:8000/android-index.json (find your local IP with a command like
ipconfig getifaddr en0)
If you are loading in your update into a development client by passing in a URL string, you will need to pass in an URL pointing to your JSON manifest file.
Here is an example URL from localhost:
When Expo CLI bundles your update, minification is always enabled. In order to see the original source code of your update for debugging purposes, you can generate source maps. Here is an example workflow:
expo export --dump-sourcemap --public-url <your-url>. This will also export your bundle sourcemaps in the
debug.html file will also be created at the root of your output directory.
- In Chrome, open up
debug.html and navigate to the
Source tab. In the left tab there should be a resource explorer with a red folder containing the reconstructed source code from your bundle.
As new Expo SDK versions are released, you may want to serve multiple versions of your app from your server endpoint. For example, if you first released your app with SDK 29 and later upgraded to SDK 30, you'd want users with your old standalone binary to receive the SDK 29 version, and those with the new standalone binary to receive the SDK 30 version.
In order to do this, you can run
expo export with some merge flags to combine previously exported updates into a single multiversion update which you can serve from your servers.
Here is an example workflow:
Release your update with previous Expo SDKs. For example, when you released SDK 29, you can run
expo export --output-dir sdk29 --public-url <your-public-url>. This exports the current version of the update (SDK 29) to a directory named
Update your app and include previous Expo SDK versions. For example, if you've previously released SDK 28 and 29 versions of your app, you can include them when you release an SDK 30 version by running
expo export --merge-src-dir sdk29 --merge-src-dir sdk28 --public-url <your-url>. Alternatively, you could also compress and host the directories and run
expo export --merge-src-url https://examplesite.com/sdk29.tar.gz --merge-src-url https://examplesite.com/sdk28.tar.gz --public-url <your-url>. This creates a multiversion update in the
dist output directory. The
bundle folders contain everything that the source directories had, and the
index.json file contains an array of the individual
index.json files found in the source directories.
By default, all assets are hosted from an
path resolving from your
). You can override this behavior in the
field of your
. All relative URL's will be resolved from the
Most of the fields in the
index.json files are the same as in
app.json. Here are some fields that are notable in
publishedTime: These fields are generated by
expo export and used to determine whether or not an OTA update should occur.
bundleUrl: This points to the path where the app's bundles are hosted. They are also used to determined whether or not an OTA update should occur.
slug: This should not be changed. Your app is namespaced by
slug, and changing this field will result in undefined behavior in the Expo SDK components such as
assetUrlOverride: The path which assets are hosted from. It is by default
./assets, which is resolved relative to the base
public-url value you initially passed in.