Docs-logo

Expo

Get Started
EAS
Slash-shortcut-icon
Hamburger-icon

Debugging guide

It's important to tell the current state of our app at any given time. We built EAS Update with this in mind. Once we know which updates are running on which builds, we can make changes so that our apps are in the state we expect and desire. This guide sets out to show how we can verify our EAS Update and expo-updates configuration, so that we can find the source of problems like an app not showing a published update.

expo-updates configuration

The expo-updates library runs inside an end-user's app and makes requests to an update server to get the latest update.

When we set up EAS Update, we likely ran eas update:configure to configure expo-updates to work with EAS Update. This command makes changes to our app config (app.json/app.config.js). Here are the fields we'd expect to see:
  • runtimeVersion should be set. By default it is { "policy": "sdkVersion" }. If our project has android and ios directories, we'll have to set the runtimeVersion manually.
  • updates.url should be a value like https://u.expo.dev/your-project-id, where your-project-id matches the ID of our project. We can see this ID on our website.
  • updates.enabled should not be false. It's true by default if it is not specified.
Finally, make sure that expo-updates is included in package.json. If it's not, run:
Terminal
→ expo install expo-updates

Whenever we run eas build, the expo prebuild command is run on our project on EAS' servers to unpack the android and ios directories that contain native files. This make it so EAS Build can build any project, whether it includes the native files or not.
If our project does not have android or ios directories, we can make commit any existing changes, then run expo prebuild to inspect the project state that EAS Build will act on. After running this, look for the following files: android/app/src/main/AndroidManifest.xml and ios/your-project-name/Supporting/Expo.plist.
In each, we expect to see configuration for the EAS Update URL and the runtime version. Here are properties we'd expect to see in each file:
AndroidManifest.xml
...
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="your-runtime-version-here"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://u.expo.dev/your-project-id-here"/>
...
Expo.plist
...
<key>EXUpdatesRuntimeVersion</key>
<string>your-runtime-version-here</string>
<key>EXUpdatesURL</key>
<string>https://u.expo.dev/your-project-id-here</string>
...

To debug the state of EAS Update in our project, we'll need to look at multiple spots in the system. Below is a diagram of how EAS Update works and the spots that are useful to inspect when finding the root cause of an issue. In the sections following, we'll inspect and verify these spots and more.
Map of debugging spots

Builds have a property named channel, which EAS Update uses to link to a branch. A channel is often given to multiple platform-specific builds. For instance, we might have an Android build and an iOS build, both with a channel named "production".

To verify that a build has a specific channel, make sure that in eas.json, there is a channel property:
{
  "build": {
    "preview": {
      "distribtuion": "internal",
      "channel": "preview"
    },
    "production": {
      "channel": "production"
    }
  }
}
Then, we can run a command like eas build --profile preview to create a build with a channel named "preview".

Once a build has a channel name, we can make sure that EAS' servers know about the channel by running the following commands:
eas channel:list
or
eas channel:view [channel-name]

# Example
eas channel:view production
We'd expect the output of these commands to display the same channel name that our build has. If it's not there, we can create the channel on EAS' servers with:
eas channel:create [channel-name]

# Example
eas channel:create production

There is a link that is defined by the developer between a channel and a branch. When a channel and branch are linked, an app with a channel will get the most recent compatible update on the linked branch.
To verify which branch is linked to a channel, we can run:
eas channel:view [channel-name]

# Example
eas channel:view production
If the channel is not linked to the branch we expect, we can change the link with:
eas channel:edit [channel-name] --branch [branch-name]

# Example
eas channel:edit production --branch release-1.0

Every branch contains a list of updates. When a build makes a call for an update, we find the channel of the build, then the branch linked to that channel. Once the branch is found, EAS will return the most recent compatible update on that branch. A build and an update are compatible when they share the same runtime version and platform.
To inspect which updates are on a branch, we can run:
eas branch:view [branch-name]

# Example
eas branch:view production
The output of this command will show us a list of updates and their runtime versions and platforms. From this list, we should be able to figure out which update should apply to a given build, by matching the build's runtime version and platform to update's runtime version and platform. The most recent update that is compatible will be available for a build to download and execute.

To create and publish an update, we can run the following command:
Terminal
→ eas update
After publishing, the output will display the branch and the runtime version. This info can help us verify that we're creating an update with the configuration we expect.

If we've made builds and updates with EAS, we can view the state of our project on our website on our project's deployments tab. We use the term deployments to refer to the entire system of builds and their updates. The system includes builds, channels, branches, updates, runtime versions, and platforms.
The EAS website has a page that shows the current state of our apps. We can view it at https://expo.dev/accounts/[account]/projects/[project]/deployments.

After verifying expo-updates and EAS Update configurations, we can move on to debugging how our project is interacting with updates.

The expo-updates library exports a variety of functions to interact with updates once the app is already running. In certain cases, making a call to fetch an update and seeing an error message can help us narrow down the root cause.
We can write the following code then make a simulator build of our project to see if there are errors fetching updates:
import { View, Button } from 'react-native';
import * as Updates from 'expo-updates';

function App() {
  async function onFetchUpdateAsync() {
    try {
      const update = await Updates.checkForUpdateAsync();

      if (update.isAvailable) {
        await Updates.fetchUpdateAsync();
        await Updates.reloadAsync();
      }
    } catch (error) {
      alert(`Error fetching latest Expo update: ${error}`);
    }
  }

  return (
    <View>
      <Button title="Fetch update" onPress={onFetchUpdateAsync} />
    </View>
  );
}

Another way to identify the root cause of an issue is to look at the network requests that the app is making to EAS servers, then viewing the responses. We recommend using a progam like Proxyman or Charles Proxy to watch network requests from our app.
With either program, we'll need to follow their instructions for installing an SSL certificate, so that the program can decode HTTPS requests. Once that's set up in a simulator or on an actual device, we can open our app and watch requests.
The requests we're interested in are from https://u.expo.dev and from https://assets.eascdn.net. Responses from https://u.expo.dev will contain an update manifest, which specifies which assets the app will need to fetch to run the update. Responses from https://assets.eascdn.net will contain assets, like images, font files, etc that are required for the update to run.
When inspecting the request to https://u.expo.dev, we can look for the following request headers:
  • Expo-Runtime-Version: this should make the runtime version we made our build and update with.
  • expo-channel-name: this should be the channel name specified in the eas.json build profile.
  • Expo-Platform: this should be either "android" or "ios".
As for all requests, we expect to see either 200 response codes, or 304 if nothing has changed.
Below is a screenshot showing the request of a successful update manfiest request:
Successful manifest request

When building a project into an app, there can be multiple steps that alter the output of expo prebuild. After making a build, it is possible to open the build's contents and inspect native files to see its final configuration.
Here are the steps for inspecting an iOS simulator build on macOS:
  1. Create an iOS simulator build of the app using EAS Build. This is done by adding "ios": { "simulator": true } to a build profile.
  2. Once the build is finished, download the result and unzip it.
  3. Then, right click on the app and select "Show Package Contents".
  4. From there, we can inspect the Expo.plist file.
Inside the Expo.plist file, we expect to see the following configurations:
...
<key>EXUpdatesRequestHeaders</key>
<dict>
  <key>expo-channel-name</key>
  <string>your-channel-name</string>
</dict>
<key>EXUpdatesRuntimeVersion</key>
<string>your-runtime-version</string>
<key>EXUpdatesURL</key>
<string>https://u.expo.dev/your-project-id</string>
...

When we publish an update with EAS Update, it creates a /dist folder in the root of our project locally, which includes the assets that were uploaded as a part of the update.
Dist directory

When an update is published with EAS Update, we create a manifest that end-user app's request. The manifest has information like which assets and versions are needed for an update to load. We can inspect the manifest by going to a specific URL in a browser or by using curl.
Inside our project's app config (app.json/app.config.json), the URL we can GET is under updates.url.
This url is EAS' "https://u.expo.dev" domain, followed by the project's ID on EAS' servers. If we go to the URL directly, we'll see an error about missing a header. We can view a manifest by adding three query parameters to the URL: runtime-version, channel-name, and platform. If we published an update with a runtime version of 1.0.0, a channel of production and a platform of android, the full URL you could visit would be similar to this:
https://u.expo.dev/your-project-id?runtime-version=1.0.0&channel-name=production&platform=android

It may be helpful to see which assets are included in our update bundle. We can see a list of named assets by running:
Terminal
→ expo export --experimental-bundle

Once we've found the root cause of the issue, there are various mitigation steps we might want to take. One of the most common problems is pushing an update that has a bug inside it. When this happens, we can re-publish a previous update to resolve the issue.

The fastest way to "undo" a bad publish is to re-publish a known good update. Imagine we have a branch with two updates:
branch: "production"
updates: [
  update 2 (id: xyz2) "fixes typo"     // bad update
  update 1 (id: abc1) "updates color"  // good update
]
If "update 2" turned out to be a bad update, we can re-publish "update 1" with a command like this:
eas update --branch [branch-name] --republish --group [update-group-id]

# Example
eas update --branch production --republish --group abc1
The example command above would result in a branch that now appears like this:
branch: "production"
updates: [
  update 3 (id: def3) "updates color"  // re-publish of update 1 (id: abc1)
  update 2 (id: xyz2) "fixes typo"     // bad update
  update 1 (id: abc1) "updates color"  // good update
]
Since "update 3" is now the most recent update on the "production" branch, all users who query for an update in the future will receive "update 3" instead of the bad update, "update 2".
While this will prevent all new users from seeing the bad update, users who've already received the bad update will run it until they can download the latest update. Since mobile networks are not always able to download the most recent update, sometimes users may run a bad update for a long time. When viewing error logs for our app, it's normal to see a lingering long tail of errors as our users' apps get the most recent update or build. We'll know we solved the bug when we see the error rate decline dramatically; however, it likely will not disappear completely if we have a diverse user base across many locations and mobile networks.

Still having issues with EAS Update? Provide us with a reproduction repo in our forums. Also, feel free to ask in the #updates channel on our community Discord, or contact us directly.