Get Started

Migrating from "expo build"

The purpose of this reference page is to call out some of the practical differences that you may need to account for when migrating your Expo managed app from expo build ("classic builds") to EAS Build. If this is your first time using EAS Build, you can use this page as a companion to "Creating your first build".
One of the goals with EAS Build is to make it as easy as possible to migrate from expo build; for example, your app signing credentials will be automatically re-used, and the Expo SDK and your app.json configuration will all work the same as before. That said, there are some differences in the build process that may require additional configuration or small code changes.

SDK 41+ apps are supported

EAS Build only supports SDK 41+ managed projects. You must upgrade your project to migrate to EAS Build.

Selecting a native appearance mode with userInterfaceStyle (or android.userInterfaceStyle) in the project app.json will only work on Android if expo-system-ui is installed in the project. This is because expo-system-ui includes code for locking the interface natively based on the app.json. Run expo install expo-system-ui to add the library. This feature is only supported in Expo SDK +43.

Selecting the root background color (for native modals and flipping orientations) with ios.backgroundColor in the project app.json will only work on iOS if expo-system-ui is installed in the project. This is because expo-system-ui includes code for setting the color natively based on the app.json. Run expo install expo-system-ui to add the library. This feature is only supported in Expo SDK +43. You can also remove references to RCTRootViewBackgroundColor in the AppDelegate.m file as this is now handled inside the expo-system-ui module.

Selecting the navigation bar interaction behavior with androidNavigationBar.visible in the project app.json will only work on Android if expo-navigation-bar is installed in the project. Also consider migrating away from this property as the underlying Android APIs are deprecated: Learn more. Run expo install expo-navigation-bar to install the library. This feature is only supported in Expo SDK +43.

Configuring the resizeMode or positioning of the splash screen with splash (or android.splash) in the project app.json will only work on Android if expo-splash-screen is installed in the project. Run expo install expo-splash-screen to install the library. This feature is only supported in Expo SDK +43.

This often results in massive reductions in app size; managed apps built with EAS Build can be in the order of 10x smaller than the same app built with expo build (learn why). The tradeoff here is that you need to be careful when publishing updates in order to avoid publishing an incompatible JavaScript bundle. Learn more in updates.

EAS Build builds your app like other CI services — in short, the entire project is uploaded securely to the cloud, then it is downloaded by a build server, the dependencies are installed, and the build is run (learn more). Everything needed to build your app must be included in the project that is uploaded. The default mechanism for packaging your project is roughly equivalent to git clone --depth 1, and so anything that is in your .gitignore will not be uploaded (learn more in "How projects are uploaded to EAS Build").
Developers often run into this with their "Google Services File", which they reference in their app.json / app.config.js but ignore in Git. If anything in your project is ignored in Git but necessary for a successful build, you can either remove it from .gitignore and commit it, or encode with base64 and store in EAS Secrets, then decode at build time.

You may be using expo build:[ios|android] --config app.production.json to switch app configuration files used by your project this is not supported in EAS Build, but it's easy to migrate to an alternative. Read more: "Migrating away from the --config flag in Expo CLI".

With classic builds, the default behavior is to automatically publish your app bundle as an update prior to running a build. This had some unintended consequences; for example, sometimes developers would run a build and be surprised to learn that their existing app was updated as a side effect.
With EAS Build, expo publish is not run as part of the build process. Instead, the JavaScript bundle is generated locally on EAS Build at build time and directly embedded in the app.
Because we no longer publish at build time, postPublish hooks in app.json will not be executed on build. If you use Sentry, be sure to update sentry-expo to the latest version and follow the updated instructions in the README. If you have other custom postPublish hooks, you can follow the same approach used in sentry-expo to support postPublish hook type of behavior.

Given that we no longer publish the app prior to builds, there is no update manifest available until the app has download an update. Usually this means that at least for the first launch of the app you won't have some fields available. If you are using Constants.manifest to access update fields, in particular Constants.manifest.releaseChannel, you should switch to Updates.releaseChannel instead.

The Constants.appOwnership field no longer exists in standalone apps produced by EAS Build. If you were previously testing the environment with something like const isStandaloneApp = Constants.appOwnership === "standalone" then you can invert the logic: const isStandaloneApp = Constants.appOwnership !== "expo".

With classic builds, assetBundlePatterns serves two purposes:
  1. Assets that match the given patterns are bundled in the binary at build time.
  2. Assets that match the given patterns determine the contents of an "atomic" update bundle. All of the files matching assetBundlePatterns need to be downloaded before an update is considered ready to launch.
Only the second purpose applies with the new build system. All assets referenced in your app source code are bundled into your app binary at build time, the same as in a default React Native app assetBundlePatterns is not used to determine what assets to bundle in the binary, it's only used for update bundles.

If your app depends on a custom "main" entry point, you will need to remove that field from package.json and then create index.js in the root of your project and use registerRootComponent to register your root component. For example, if your app root component lives in src/App.tsx, your index.js should look like the following:
import { registerRootComponent } from 'expo';
import App from './src/App';

Support for custom entry points is in progress and is coming soon.

Classic builds had no knowledge of your repository set up, you could use a monorepo or birepo or trirepo, the service was entirely indifferent. As long as you were able to publish a bundle, that's all that was needed. EAS Build needs to be able to install all of your project dependencies and essentially set up your development environment inside of a worker, so in some cases that will require some additional configuration. Learn more: "How to set up EAS Build with a monorepo".
Work is in progress to improve monorepo support for EAS Build managed projects. We recommend using expo-yarn-workspaces.

If you use environment variables in your app.config.js or in your app source code (eg: with babel-plugin-inline-dotenv), you need to define these variables for your build profiles or in secrets, as described in "Environment variables and secrets". With classic builds this was not necessary because your app JavaScript was always built on your development machine (when you publish the app bundle prior to building), but now the app JavaScript is built in an EAS Build worker.

Learn more about how to securely store your NPM_TOKEN on EAS Build: "Using private npm packages".

You will need to remove expo-branch from your app to build it with EAS Build. For EAS Build, you need to use the official react-native-branch with @config-plugins/react-native-branch instead.

In projects built with expo build the native primitives required by AWS Amplify are included in every app. This is not the case in EAS Build, and so you must install amazon-cognito-identity-js in order to link the native module depended on by AWS Amplify libraries.

Most apps do not use this format and support for it adds ~3.4 MB to the final app size, so it is omitted by default. You can enable it by switching expo.webp.animated=false to expo.webp.animated=true in android/ This forums post provides an example of a config plugin for making this change.

expo/metro-config is a versioned re-export of @expo/metro-config.
Previously, with classic builds, your metro.config.js might have looked something like:
const { getDefaultConfig } = require('expo/metro-config');

const defaultConfig = getDefaultConfig(__dirname);

module.exports = {
  resolver: {
    assetExts: [...defaultConfig.resolver.assetExts, 'db'],
In the example above, you're only exporting part of the default config, but EAS Build requires the full config. To do that, you should modify defaultConfig directly, and then return the resulting object, like this:
const { getDefaultConfig } = require('expo/metro-config');

const defaultConfig = getDefaultConfig(__dirname);


module.exports = defaultConfig;
If you don't set up your metro.config.js file properly, your assets could fail to load in release builds.

Having trouble migrating? Join us in the #eas channel on the Expo Discord and let us know, we'll do our best to help.