# Expo Application Services (EAS) Documentation
Expo Application Services (EAS) are deeply integrated cloud services for Expo and React Native apps, from the team behind Expo.
## Introduction
## Configuration with eas.json
Learn about available properties for EAS Build and EAS Submit to configure and override their default behavior from within your project.
**eas.json** is the configuration file for EAS CLI and services. You can find the complete reference of all available schema properties for [EAS Build](/build/introduction) and [EAS Submit](/submit/introduction) on this page.
> **info** To learn more about how a project using EAS services is configured with **eas.json**, see [Configure EAS Build with eas.json](/build/eas-json/) and [Configure EAS Submit with eas.json](/submit/eas-json/).
## EAS Build
The following properties are available in the schema for the `build` key in **eas.json**.
Note: Example schema of multiple build profiles
---
```json eas.json
{
"build": {
"base": {
"node": "12.13.0",
"yarn": "1.22.5",
"env": {
"EXAMPLE_ENV": "example value"
},
"android": {
"image": "default",
"env": {
"PLATFORM": "android"
}
},
"ios": {
"image": "latest",
"env": {
"PLATFORM": "ios"
}
}
},
"development": {
"extends": "base",
"developmentClient": true,
"env": {
"ENVIRONMENT": "development"
},
"android": {
"distribution": "internal",
"withoutCredentials": true
},
"ios": {
"simulator": true
}
},
"staging": {
"extends": "base",
"env": {
"ENVIRONMENT": "staging"
},
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"extends": "base",
"env": {
"ENVIRONMENT": "production"
}
}
}
}
```
---
### Common properties for native platforms
### Android-specific options
### iOS-specific options
## EAS Submit
The following properties are available in the schema for the `submit` key in **eas.json**.
Note: Example schema of with production profile
---
```json eas.json
{
"cli": {
"version": ">= 0.34.0"
},
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json",
"track": "internal"
},
"ios": {
"appleId": "john@turtle.com",
"ascAppId": "1234567890",
"appleTeamId": "AB12XYZ34S"
}
}
}
}
```
---
### Android-specific options
### iOS-specific options
## Environment variables in EAS
Learn how to use and manage environment variables in EAS with examples.
[Environment variables in Expo](/guides/environment-variables) describe how to use environment variables with the Expo framework and **.env** files to set environment variables that can be inlined in your JavaScript code. Expo CLI will substitute prefixed variables in your code (for example, `process.env.EXPO_PUBLIC_VARNAME`) with the corresponding environment variable values in **.env** files on your development machine.
Since EAS Build and Workflows jobs run on a remote server, **.env** files may not be available. For instance, if **.env** files are excluded from your project, it is because they are listed in **.gitignore** or not committed to your local version control system.
Additionally, you may want to use environment variables outside of your project code to customize your app binary at build time, like setting a bundle identifier or a private key for an error reporting service. To accommodate for those needs we have a separate (but compatible) mechanism for managing environment variables in EAS, which is focused on storing and managing environment variables on EAS servers and synchronizing them for local development.
This guide describes how to use and manage environment variables in EAS with key practical examples.
## Key concepts
Note: Environments in EAS
---
Currently, EAS supports three environments for environment variables: `development`, `preview` and `production`. Environments are independent sets of environment variables that can be used to customize your app in different contexts. For example, you might want to use different API keys for development and production, or different bundle identifiers for different App Store releases. Environments allows you to do so. Every EAS Build and Workflows job runs using environment variables from one of the available environments. You can also use environments for updates, allowing you to use the same set of environment variables for your build jobs. You can do this when publishing an update by providing the `--environment` flag.
Environment variables can be assigned to multiple environments and have the same value across all of them, or be created for a single environment, so that you can set a specific value for a single environment.
---
Note: Project-wide environment variables
---
Project-wide environment variables are specific to a single EAS project. You can view and manage them by navigating to the [Environment Variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) page on your project.
These environment variables are available in any jobs that run on EAS servers and updates for this project. They can also be pulled locally for development if their visibility setting allows it.
---
Note: Account-wide environment variables
---
Account-wide environment variables are available across all of your projects in your EAS account. You can view and manage them by navigating to [Environment Variables](https://expo.dev/accounts/[account]/settings/environment-variables) page on your account.
They are available in jobs that run on EAS servers and updates, together with project-wide variables for a project. You can pull them locally or read outside of EAS servers if their visibility setting allows it.
---
Note: Visibility settings for environment variables
---
There are three different visibility settings:
| Visibility | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Plain text | Visible on the website, in EAS CLI, and in logs. |
| Sensitive | Obfuscated in build and workflow jobs logs. You can use a toggle to make them visible on the website. They're also readable in EAS CLI. |
| Secret | Not readable outside of the EAS servers, including on the website and in EAS CLI. They are obfuscated in build and workflow jobs logs. |
> **warning** Always remember that **anything that is included in your client side code should be considered public and readable to any individual that can run the application**. Secret type environment variables are intended to be used to provide values to an EAS Build or Workflows job so that they may be used to alter how a job runs. For example, a good use case is setting an `NPM_TOKEN` to install private packages from npm, or a setting a Sentry API key to create a release and upload your source maps. Secrets do not provide any additional security for values that you end up embedding in your application itself.
---
## Creating and using environment variables
The sections below use the following common environment variables as examples:
- `EXPO_PUBLIC_API_URL`: a plain text [`EXPO_PUBLIC_`](/guides/environment-variables/) variable that holds the URL of the API server
- `APP_VARIANT`: a plain text variable to select an [app variant](/tutorial/eas/multiple-app-variants/)
- `GOOGLE_SERVICES_JSON`: a secret file variable to supply your git ignored **google-services.json** file to the build job
- `SENTRY_AUTH_TOKEN`: a sensitive variable that holds the authentication token for Sentry used to upload source maps after builds and updates
### Use environment variables in your code
The environment variables with the [`EXPO_PUBLIC_`](/guides/environment-variables) prefix are available as `process.env` variables in your app's code. This means you can use them to dynamically configure your app behavior based on the values from environment variables.
```tsx
import { Button } from 'react-native';
function Post() {
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
async function onPress() {
await fetch(apiUrl, { ... })
}
return ;
}
```
In above example, `EXPO_PUBLIC_API_URL` is used to dynamically set the API URL for the fetch request.
> **warning** Do not store sensitive information in `EXPO_PUBLIC_` variables, such as private keys. These variables will be visible in plain-text in your compiled app.
Other variables, without the `EXPO_PUBLIC_` prefix, can be used during app config resolution. An example of this is the `APP_VARIANT` variable used to determine the app name and package name or bundle identifier based on the selected app variant.
```js app.config.js
const IS_DEV = process.env.APP_VARIANT === 'development';
const IS_PREVIEW = process.env.APP_VARIANT === 'preview';
const getUniqueIdentifier = () => {
if (IS_DEV) {
return 'com.yourname.stickersmash.dev';
}
if (IS_PREVIEW) {
return 'com.yourname.stickersmash.preview';
}
return 'com.yourname.stickersmash';
};
const getAppName = () => {
if (IS_DEV) {
return 'StickerSmash (Dev)';
}
if (IS_PREVIEW) {
return 'StickerSmash (Preview)';
}
return 'StickerSmash: Emoji Stickers';
};
export default {
name: getAppName(),
ios: {
bundleIdentifier: getUniqueIdentifier(),
},
android: {
package: getUniqueIdentifier(),
},
};
```
The `GOOGLE_SERVICES_JSON` is a secret file variable in that is not readable outside of EAS servers and is used to provide the git ignored **google-services.json** file to the build job. To use it in the app config, you can use the `process.env` variable and provide a fallback value in case the variable is not set (for local development when you usually have it inside your project's repository).
```js app.config.js
export default {
android: {
googleServicesFile: process.env.GOOGLE_SERVICES_JSON ?? '/local/path/to/google-services.json',
},
};
```
### Create environment variables
To create environment variables on EAS servers, you can use the [environment variables creation form](https://expo.dev/accounts/[account]/projects/[project]/environment-variables/new) page or `eas env:create` command.
In the form, you can specify the name, value, environment(s) and visibility for the variable.
Created environment variables will be available in the list on the [Environment Variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) page on the Expo website.
Based on the environment variables in this example, the list will look like this:
In above example, the `SENTRY_AUTH_TOKEN` can be treated as a sensitive environment variable. It is used to authenticate Sentry to upload source maps after builds and updates, so it has to be accessible outside of EAS servers.
### Pull environment variables for your local development
The easiest way to use the EAS environment variables for local development is to pull them into a **.env** file using the `eas env:pull --environment environment` command. You can also use the Export option on the [Expo website](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) to download the file and store it inside your project.
Run the following command to create a **.env** file in the root of your project:
```sh
$ eas env:pull --environment development
```
The created **.env** file will look like this:
```bash .env.local
# Environment: development
APP_VARIANT=development
EXPO_PUBLIC_API_URL=https://staging.my-api-url.mycompany.com
# GOOGLE_SERVICES_JSON=***** (secret variables are not available for reading)
SENTRY_AUTH_TOKEN=token
```
The downloaded `EXPO_PUBLIC_` variables are available for local development when using the `npx expo start` command. The `GOOGLE_SERVICES_JSON` variable is not available to be pulled to the local environment since it is a secret variable.
> **warning** It is best to add all of the **.env** files to **.gitignore** to avoid committing them to your repository and exposing sensitive information. Committing a **.env** file may additionally lead to environment variables resolution conflicts.
### Use environment variables with EAS Build
To have a full control over the environments used for your builds, you can specify the [`environment`](/eas/json#environment) field in the build profiles settings in the **eas.json** file.
```json eas.json
{
"build": {
"development": {
"environment": "development"
},
"preview": {
"environment": "preview"
},
"production": {
"environment": "production"
},
"my-profile": {
"environment": "production"
}
}
}
```
All of the environment variables from the selected environment will be used during the build process. Plain text and sensitive variables will be available when resolving build configuration based on the dynamic app config in EAS CLI as well.
> **info** The environment variables of secret type are not available during build configuration resolution in EAS CLI as they are not readable outside of the EAS servers.
In this example app, all of the environment variables except `GOOGLE_SERVICES_JSON` (which is secret) will be available during build configuration resolution in EAS CLI.
It's especially important in this context to have the correct visibility set for the `APP_VARIANT` variable so that the variable available in EAS CLI and is able to generate credentials for the correct package name and bundle identifier.
### Use environment variables with EAS Update
The most convenient way to use EAS environment variables with EAS Update is to run the `eas update` command with the `--environment` flag specified:
```sh
$ eas update --environment production
```
When the `--environment` flag is used, **only the environment variables from the specified EAS environment will be used during the update process** and won't use the **.env** files present in your project. This flag allows you to use the same environment variables while creating updates as with creating builds.
Expo CLI will substitute prefixed variables in your code (for example,`process.env.EXPO_PUBLIC_VARNAME`) with the corresponding plain text and sensitive environment variable values set on EAS servers for the environment specified with the `--environment` flag. Any `EXPO_PUBLIC_` variables in your application code will be replaced inline with the corresponding values from your EAS environment whether that is your local machine or your CI/CD server.
We recommend using the `--environment` flag to ensure the same environment variables are used both for your update and build jobs.
> **info** The secret variables will not be available during the update process as they are not readable outside of EAS servers.
Note: Using .env files with EAS Update
---
When the `--environment` flag is **not provided**, `eas update` will use the **.env** files present in your project directory for the update job and won't use the environment variables set on the EAS servers.
[Environment variables in Expo](/guides/environment-variables) describes how to use **.env** files to set and use environment variables within your JavaScript code. Expo CLI will substitute prefixed variables in your code (for example,`process.env.EXPO_PUBLIC_VARNAME`) with the corresponding environment variable values in **.env** files present on your development machine.
When you run `eas update`, all **.env** files will be evaluated when your JavaScript is bundled.
Any `EXPO_PUBLIC_` variables in your application code will be replaced inline with the corresponding values from your **.env** files that are present on the machine from which the update is published, whether that is your local machine or your CI/CD server.
> **info** When using **.env** files with EAS Update, `EXPO_PUBLIC_` variables in these files will only be used when bundling your app's JavaScript. They will not be available when evaluating **app.config.js**.
---
### Use environment variables for other commands
One way to supply non-secret EAS environment variables to other EAS commands is to use the `eas env:exec` command.
```sh
$ eas env:exec --environment production 'echo $APP_VARIANT'
```
For example, it can be useful when uploading source maps to Sentry using a [`SENTRY_AUTH_TOKEN`](/guides/using-sentry) variable after an update bundle is created.
```sh
$ eas env:exec --environment production 'npx sentry-expo-upload-sourcemaps dist'
```
### EAS Build and Workflows job runs
#### Setting the environment for your builds
To set the environment for your EAS Build jobs, you can use the `environment` option in **eas.json** and set it to one of the available environments: `development`, `preview` or `production`.
```json eas.json
{
"build": {
"development": {
"environment": "development"
},
"preview": {
"environment": "preview"
},
"production": {
"environment": "production"
},
"my-profile": {
"environment": "production"
}
}
```
If you don't set the `environment` option, we will set the environment automatically based on your build's configuration:
- `development`: for development client build
- `preview`: for other configurations
- `production`: for app store build
> **info** This resolution logic is only available for EAS CLI in version 13.3.0 and higher. For older CLI versions, we always assume the `production` environment for all builds.
Note: Built-in environment variables
---
The following environment variables are additional system environment variables exposed to each job and can be used within any build step. They are not a part of any project environment and are not available when evaluating **app.config.js** locally:
- `CI=1`: indicates this is a CI environment
- `EAS_BUILD=true`: indicates this is an EAS Build environment
- `EAS_BUILD_PLATFORM`: either `android` or `ios`
- `EAS_BUILD_RUNNER`: either `eas-build` for EAS Build cloud builds or `local-build-plugin` for [local builds](/build-reference/local-builds/)
- `EAS_BUILD_ID`: the build ID, for example, `f51831f0-ea30-406a-8c5f-f8e1cc57d39c`
- `EAS_BUILD_PROFILE`: the name of the build profile from **eas.json**, for example, `production`
- `EAS_BUILD_GIT_COMMIT_HASH`: the hash of the Git commit, for example, `88f28ab5ea39108ade978de2d0d1adeedf0ece76`
- `EAS_BUILD_NPM_CACHE_URL`: the URL of npm cache ([learn more](/build-reference/private-npm-packages))
- `EAS_BUILD_MAVEN_CACHE_URL`: the URL of Maven cache ([learn more](/build-reference/caching/#android-dependencies))
- `EAS_BUILD_COCOAPODS_CACHE_URL`: the URL of CocoaPods cache ([learn more](/build-reference/caching/#ios-dependencies))
- `EAS_BUILD_USERNAME`: the username of the user initiating the build (it's undefined for bot users)
- `EAS_BUILD_WORKINGDIR`: the remote directory path with your project
---
#### Dynamically setting environment variables during the job execution
You can also set environment variables dynamically during the job execution using the `set-env` command.
The `set-env` executable is available in the `PATH` on EAS Build workers, and can be used to set environment variables that will be visible in the next build phases.
For example, you can add the following in one of the [EAS Build hooks](/build-reference/npm-hooks/) and the environment variable `EXAMPLE_ENV` will be available until the end of the build job.
```sh
$ set-env EXAMPLE_ENV "example value"
```
#### Accessing environment variables
After creating an environment variable, you can read it on subsequent EAS Build jobs with `process.env.VARIABLE_NAME` from Node.js or in shell scripts as `$VARIABLE_NAME`.
## Managing environment variables
Note: Managing environment variables using Expo dashboard
---
The easiest way to create, read, update, export and delete environment variables is to use the Expo website. Navigate to the [Environment Variables page on your project](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) or [Environment Variables page on your account](https://expo.dev/accounts/[account]/settings/environment-variables) to manage your environment variables.
---
Note: Managing environment variables using EAS CLI
---
To manage environment variables using EAS CLI, you can use the `eas env:create`, `eas env:update`, `eas env:list`, and `eas env:delete` commands.
You can additionally use `eas env:pull` command to pull environment variables from EAS servers to your local **.env** file for development.
See the [EAS CLI command reference](https://github.com/expo/eas-cli/blob/main/packages/eas-cli/README.md) for more information about these commands.
---
## Common questions
Note: What is the recommended workflow for using environment variables in my EAS project?
---
One possible way to efficiently work with environment variables in your EAS projects is to:
#### Use correct visibility settings
Make sure to set the visibility of your environment variables to the appropriate level. Avoid setting excessive secret visibility to `EXPO_PUBLIC_` variables that are used in your app's JavaScript code or are used to resolve your app's configuration. Be aware that environment variables with secret visibility are not readable outside of EAS servers, and can't be pulled locally for development or to bundle your app's JavaScript code for updates.
#### Add .env files to .gitignore
To avoid confusing overrides during cloud jobs and leaking sensitive information, add **.env** files to your **.gitignore** file.
#### Use the `--environment` flag with `eas update`
When publishing updates, use the `--environment` flag with the `eas update` command to ensure that the same environment variables are used for your updates as your build jobs.
When the `--environment` flag is provided, `eas update` will use the environment variables on EAS servers for the update job and won't use the **.env** files present in your project often used for local development.
#### Sync the environment variables for local development using `eas env:pull`
You can use the `eas env:pull` command to pull environment variables from EAS servers to your local **.env** file for development. The ideal environment that can be used for this purpose is the `development` environment, as it's the default environment used for development builds.
#### Explicitly specify the environment for your builds
Explicitly set the [`environment`](/eas/json#environment) value in **eas.json** for your build profiles to ensure that the correct environment variables are always used for your build jobs and you have full control over this process.
---
Note: Can I set my environment variables on a CI provider when triggering the build using command?
---
Environment variables must be defined on EAS servers to be made available to EAS Build builders. If you are triggering builds from CI the same rule applies, and you should be careful to not confuse setting environment variables on GitHub Actions (or the provider of your choice) with setting environment variables and secrets on EAS servers.
---
Note: How do environment variables work for my development builds?
---
Environment variables set in your build profile that impact **app.config.js** will be used for configuring the development build.
When you run `npx expo start` to load your app inside of your development build, only environment variables that are available on your development machine will be used.
---
Note: Can I use file environment variables in my EAS project?
---
In addition to setting strings as values, you can also upload files as the value of an environment variable.
One common use case of using file environment variable is passing a git ignored **google-services.json** configuration file to a build job. During the job run, the file will be created in a location outside of the project directory and the path to the file will be assigned to the environment variable (`GOOGLE_SERVICES_JSON=/path/to/google-services.json`). For example, you can then set `android.googleServicesFile` in your app config to the value of the `GOOGLE_SERVICES_JSON` environment variable to use this file when executing the build or workflow job.
```js app.config.js
export default {
android: {
googleServicesFile: process.env.GOOGLE_SERVICES_JSON ?? '/local/path/to/google-services.json',
},
};
```
---
Note: Differences between handling environment variables in EAS CLI and Expo CLI
---
One of the differences between using environment variables with the Expo framework and EAS is that EAS CLI itself does not support loading **.env** files to set environment variables when resolving the expo config. Instead, it's recommended to use the EAS environment variables management system with EAS CLI commands to set environment variables for your build jobs and updates to avoid confusion, and ensure that exactly the same environment variables are used both for:
- Local app config resolution, done by EAS CLI when preparing the expo config
- Remote jobs happening on EAS servers, which often don't have access to your local **.env** files that are git ignored
Using `eas update` is the one exception to this rule. By default, for backward compatibility reasons, it uses **.env** files present in your project directory to set environment variables for the update job, the same way [Expo CLI](/guides/environment-variables) does (it executes the `npx expo export` command under the hood).
To ensure that you can use the same environment variables in your updates as your build jobs, you can use the `--environment` flag with the `eas update` command to force it to use **only** the environment variables set on the EAS servers instead of the **.env** files present in your project directory. It will ignore environment variables from **.env**.
---
Note: Are there any limitations to using environment variables in EAS?
---
- Environment variable value size is limited to 32 KiB for environment variables with secret visibility and 4 KiB for other visibility types.
- You can create up to 100 account-wide environment variables for each Expo account and 100 project-specific environment variables for each app.
---
# EAS Workflows
## Get started with EAS Workflows
Learn how to use EAS Workflows to automate your React Native CI/CD development and release processes.
Builds, submissions, updates, and more are all a part of delivering your app to users. EAS Workflows consist of a sequence of jobs, which help you and your team get things done. With workflows, you can build your project, run end-to-end tests, submit that build to the app stores, and then run custom scripts after the submission is complete. Since each job can have prerequisites and conditionals, you can automate your and your team's CI/CD for your React Native project.
## Get started
Learn what EAS Workflows are and how to use them to automate your CI/CD processes with the following video tutorial:
Video Tutorial: [Watch: Get Started with EAS Workflows](https://www.youtube.com/watch?v=OJ2u9tQCpr4)
Share the following slide in your next team meeting to discuss what EAS Workflows are and how they can help your team:
Note: How do workflows compare to other CI services?
---
EAS Workflows are designed to help you and your team release your app. It comes preconfigured with pre-packaged job types that can build, submit, update, run Maestro tests, and more. All job types run on EAS, so you'll only have to manage one set of YAML files, and all the artifacts from your job runs will appear on [expo.dev](https://expo.dev/).
Other CI services, like CircleCI and GitHub Actions, are more generalized and have the ability to do more than workflows. However, those services also require you to understand more about the implementation of each job. While that is necessary in some cases, workflows help you get common tasks done quickly by pre-packaging the most essential types of jobs for app developers. In addition, workflows are designed to provide you with the fastest possible cloud machine for the task at hand, and we're constantly updating those for you.
EAS Workflows are great for operations related to your Expo apps, while other CI/CD services will provide a better experience for other types of workflows.
---
## Set up your project
If you haven't already, you'll need to create a project and sync it with EAS:
Note: Create a project and sync it with EAS
---
You can create a new project with the following command:
{' '}
```sh
$ npx create-expo-app@latest
```
Once you've created the project, login with your account:
{' '}
```sh
$ npx eas-cli@latest login
```
Finally, link the project you have locally with EAS:
```sh
$ npx eas-cli@latest init
```
---
## Configure your project
EAS Workflows require a GitHub repository that's linked to your EAS project to run. You can link a GitHub repo to your EAS project with the following steps:
- Navigate to your project's [GitHub settings](https://expo.dev/accounts/%5Baccount%5D/projects/%5BprojectName%5D/github).
- Follow the UI to install the GitHub app.
- Select the GitHub repository that matches the Expo project and connect it.
## Write a workflow
First, create a directory named **.eas/workflows** at the root of your project with a YAML file inside of it. For example: **.eas/workflows/hello-world.yml**.
Now we're ready to write the contents of **hello-world.yml**. Each workflow consists of three top-level elements:
- `name`: defines the name of the workflow. For example: "Hello World"
- `on`: defines when this workflow should be triggered. For example, when pushing a new commit to a GitHub branch.
- `jobs`: a sequence of jobs which can depend on and pass data between each other. For example: a job that runs a unit test followed by a job that builds your project into an app.
Here's an example of a workflow that prints "Hello, world":
```yaml .eas/workflows/hello-world.yml
name: Hello World
on:
push:
branches: ['*']
jobs:
Hello World:
steps:
- run: echo "Hello, World"
```
Here's another example that creates and submits an iOS build of a project on every push to every branch. This is similar to running `eas build --platform ios --profile production --auto-submit`:
```yaml .eas/workflows/ios-build-and-submit.yml
name: Release iOS app
on:
push:
branches: ['*']
jobs:
build:
type: build
params:
platform: ios
profile: production
submit:
needs: [build]
type: submit
params:
build_id: ${{ needs.build.outputs.build_id }}
```
> **info** Download the [Expo Tools VS Code extension](https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools) to get descriptions and autocompletions for your workflow files.
## Run your workflow
Once you have a workflow file and your project is connected to Expo's GitHub app, you can trigger your workflow by pushing a commit to your GitHub repository. For the workflow to run, you'll need to make sure the trigger (defined with `on`) you defined in your workflow is met.
Alternatively, you can trigger a workflow manually by running the following command:
```sh
$ npx eas-cli@latest workflow:run .eas/workflows/.yml
```
Once you do, you can see your workflow running on your project's [workflows page](https://expo.dev/accounts/[account]/projects/[projectName]/workflows).
> Got feedback or feature requests? Send us an email at workflows@expo.dev.
## Example CI/CD workflows
Common CI/CD workflows that are useful for your project.
The following workflows are examples of how you can use EAS Workflows to automate your development and release processes. They can help you and your team develop, review each other's PRs, and release changes to your users.
## Development builds workflow
[Development builds](/develop/development-builds/introduction/) are specialized builds of your project that include Expo's developer tools. These types of builds include all native dependencies inside your project, enabling you to run a production-like build of your project on a simulator, emulator, or a physical device.
To get started, you'll need to configure your project and devices to build and run development builds. Learn how to set up your environment for development builds with the following guides:
After you've configured your project and devices, add the following build profiles to your **eas.json** file.
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"development-simulator": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
}
}
}
```
The following workflow creates a build for each platform and for both physical devices, Android emulators, and iOS simulators. They all will run in parallel.
```yaml .eas/workflows/create-development-builds.yml
name: Create development builds
jobs:
android_development_build:
name: Build Android
type: build
params:
platform: android
profile: development
ios_device_development_build:
name: Build iOS device
type: build
params:
platform: ios
profile: development
ios_simulator_development_build:
name: Build iOS simulator
type: build
params:
platform: ios
profile: development-simulator
```
Run the above workflow with:
```sh
$ eas workflow:run .eas/workflows/create-development-builds.yml
```
## Preview updates workflow
Once you've made changes to your project, you can share a preview of your changes with your team by publishing a [preview update](/review/share-previews-with-your-team/).
You can access preview updates in the development build UI and through scannable QR codes on the Expo dashboard. When publishing a preview on every commit, your team can review changes without pulling the latest changes and running them locally.
Your project needs to have [EAS Update](/eas-update/introduction/) setup to publish preview updates. You can set up your project with:
```sh
$ eas update:configure
```
After you've configured your project, create new [development builds](/develop/development-builds/create-a-build/) for each platform.
The following workflow publishes a preview update for every commit on every branch.
```yaml .eas/workflows/publish-preview-update.yml
name: Publish preview update
on:
push:
branches: ['*']
jobs:
publish_preview_update:
name: Publish preview update
type: update
params:
branch: ${{ github.ref_name || 'test' }}
```
## Deploy to production workflow
When you're ready to deliver changes to your users, you can build and submit to the app stores or you can send an over-the-air update. The following workflow detects if you need new builds, and if so, it sends them to the app stores. If new builds are not required, it will send an over-the-air update.
To set up EAS Build, follow this guide:
To set up EAS Submit, follow the Google Play Store and Apple App Store submissions guides:
And finally, you'll need to set up EAS Update, which you can do with:
```sh
$ eas update:configure
```
The following workflow runs on each push to the `main` branch and performs the following:
- Takes a hash of the native characteristics of the project using [Expo Fingerprint](/versions/latest/sdk/fingerprint/).
- Checks if a build already exists for the fingerprint.
- If a build does not exist, it will build the project and submit it to the app stores.
- If a build exists, it will send an over-the-air update.
```yaml .eas/workflows/deploy-to-production.yml
name: Deploy to production
on:
push:
branches: ['main']
jobs:
fingerprint:
name: Fingerprint
type: fingerprint
get_android_build:
name: Check for existing android build
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
profile: production
get_ios_build:
name: Check for existing ios build
needs: [fingerprint]
type: get-build
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
profile: production
build_android:
name: Build Android
needs: [get_android_build]
if: ${{ !needs.get_android_build.outputs.build_id }}
type: build
params:
platform: android
profile: production
build_ios:
name: Build iOS
needs: [get_ios_build]
if: ${{ !needs.get_ios_build.outputs.build_id }}
type: build
params:
platform: ios
profile: production
submit_android_build:
name: Submit Android Build
needs: [build_android]
type: submit
params:
build_id: ${{ needs.build_android.outputs.build_id }}
submit_ios_build:
name: Submit iOS Build
needs: [build_ios]
type: submit
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
publish_android_update:
name: Publish Android update
needs: [get_android_build]
if: ${{ needs.get_android_build.outputs.build_id }}
type: update
params:
branch: production
platform: android
publish_ios_update:
name: Publish iOS update
needs: [get_ios_build]
if: ${{ needs.get_ios_build.outputs.build_id }}
type: update
params:
branch: production
platform: ios
```
## Syntax for EAS Workflows
Reference guide for the EAS Workflows configuration file syntax.
A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration.
To get started with workflows, see [Get Started with EAS Workflows](/eas/workflows/get-started) or see [Examples](/eas/workflows/examples) for complete workflow configurations.
## Workflow files
Workflow files use YAML syntax and must have either a `.yml` or `.yaml` file extension. If you're new to YAML and want to learn more, see [Learn YAML in Y minutes](https://learnxinyminutes.com/docs/yaml/).
Workflow files are located in the **.eas/workflows** directory in your project. The **.eas** directory should be at the same level as your [**eas.json**](/build/eas-json/) file.
For example:
## Configuration Reference
Below is a reference for the syntax of the workflow configuration file.
## `name`
The human-friendly name of the workflow. This is displayed on the Expo dashboard on the workflows list page and is the title of the workflow's detail page.
```yaml
# @info #
name: My workflow
# @end #
```
## `on`
The `on` key defines which GitHub events trigger the workflow. Any workflow can be triggered with the `eas workflow:run` command, regardless of the `on` key.
```yaml
on:
# Trigger on pushes to main branch
push:
branches:
- main
# And on pull requests starting with 'version-'
pull_request:
branches:
- version-*
```
### `on.push`
Runs your workflow when you push a commit to matching branches and/or tags.
With the `branches` list, you can trigger the workflow only when those specified branches are pushed to. For example, if you use `branches: ['main']`, only pushes to the `main` branch will trigger the workflow. Supports globs. By using the `!` prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it).
With the `tags` list, you can trigger the workflow only when those specified tags are pushed. For example, if you use `tags: ['v1']`, only the `v1` tag being pushed will trigger the workflow. Supports globs. By using the `!` prefix you can specify tags to ignore (you still need to provide at least one tag pattern without it).
When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow will trigger on push events to all branches and will not trigger on tag pushes. If only one of the two lists is provided the other defaults to `[]`.
```yaml
on:
# @info #
push:
# @end #
branches:
- main
- feature/**
- !feature/test-** # other branch names and globs
tags:
- v1
- v2*
- !v2-preview** # other tag names and globs
```
### `on.pull_request`
Runs your workflow when you create or update a pull request that targets one of the matching branches.
With the `branches` list, you can trigger the workflow only when those specified branches are the target of the pull request. For example, if you use `branches: ['main']`, only pull requests to merge into the main branch will trigger the workflow. Supports globs. Defaults to `['*']` when not provided, which means the workflow will trigger on pull request events to all branches. By using the `!` prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it).
With the `types` list, you can trigger the workflow only on the specified pull request event types. For example, if you use `types: ['opened']`, only the `pull_request.opened` event (sent when a pull request is first opened) will trigger the workflow. Defaults to `['opened', 'reopened', 'synchronize']` when not provided. Supported event types:
- `opened`
- `reopened`
- `synchronize`
- `labeled`
```yaml
on:
# @info #
pull_request:
# @end #
branches:
- main
- feature/**
- !feature/test-** # other branch names and globs
types:
- opened
# other event types
```
### `on.pull_request_labeled`
Runs your workflow when a pull request is labeled with a matching label.
With the `labels` list, you can specify which labels, when assigned to your pull request, will trigger the workflow. For example, if you use `labels: ['Test']`, only labeling a pull request with the `Test` label will trigger the workflow. Defaults to `[]` when not provided, which means no labels will trigger the workflow.
You can also provide a list of matching labels directly to `on.pull_request_labeled` for simpler syntax.
```yaml
on:
# @info #
pull_request_labeled:
# @end #
labels:
- Test
- Preview
# other labels
```
Alternatively:
```yaml
on:
# @info #
pull_request_labeled:
# @end #
- Test
- Preview
# other labels
```
## `jobs`
A workflow run is made up of one or more jobs.
```yaml
jobs:
# @info #
job_1:
# ...
job_2:
# ...
# @end #
```
### `jobs.`
Each job must have an ID. The ID should be unique within the workflow and can contain alphanumeric characters and underscores. For example, `my_job` in the following YAML:
```yaml
jobs:
# @info #
my_job:
# @end #
# ...
```
### `jobs..name`
The human-friendly name of the job displayed on the workflow's detail page.
```yaml
jobs:
my_job:
# @info #
name: Build app
# @end #
```
### `jobs..environment`
Sets the [EAS environment variable](/eas/environment-variables/) environment for the job. There are three possible values:
- `production` (default)
- `preview`
- `development`
The `environment` key is available on all jobs except for the pre-packaged `build`, `submit`, and `get-build` jobs.
```yaml
jobs:
my_job:
# @info #
environment: production | preview | development
# @end #
```
### `jobs..defaults.run.working_directory`
Sets the directory to run commands in for all steps in the job.
```yaml
jobs:
my_job:
# @info #
defaults:
run:
working_directory: ./my-app
# @end #
steps:
- name: My first step
run: pwd # prints: /home/expo/workingdir/build/my-app
```
## `defaults`
Parameters to use as defaults for all jobs defined in the workflow configuration.
### `defaults.run.working_directory`
Default working directory to run the scripts in. Relative paths like "./assets" or "assets" are resolved from the app's base directory.
### `defaults.tools`
Specific versions of tools that should be used for jobs defined in this workflow configuration. Follow each tool's documentation for available values.
| Tool | Description |
| ----------- | -------------------------------------------------------------------------- |
| `node` | Version of Node.js installed via `nvm`. |
| `yarn` | Version of Yarn installed via `npm -g`. |
| `pnpm` | Version of pnpm installed via `npm -g`. |
| `bun` | Version of Bun installed by passing `bun-v$VERSION` to Bun install script. |
| `ndk` | Version of Android NDK installed through `sdkmanager`. |
| `bundler` | Version of Bundler that will be passed to `gem install -v`. |
| `fastlane` | Version of fastlane that will be passed to `gem install -v`. |
| `cocoapods` | Version of CocoaPods that will be passed to `gem install -v`. |
Example of workflow using `defaults.tools`:
```yaml .eas/workflows/publish-update.yml
name: Set up custom versions
defaults:
tools:
node: latest
yarn: '2'
fastlane: 2.224.0
on:
push:
branches: ['*']
jobs:
setup:
steps:
- name: Check Node version
run: node --version # should print a concrete version, like 23.9.0
- name: Check Yarn version
run: yarn --version # should print a concrete version, like 2.4.3
```
## Control flow
You can control when a job runs with the `needs` and `after` keywords. In addition, you can use the `if` keyword to control whether a job should run based on a condition.
### `jobs..needs`
A list of job IDs whose jobs must complete successfully before this job will run.
```yaml
jobs:
test:
steps:
- uses: eas/checkout
- uses: eas/use_npm_token
- uses: eas/install_node_modules
- name: tsc
run: yarn tsc
build:
# @info #
needs: [test] # This job will only run if the 'test' job succeeds
# @end #
type: build
params:
platform: ios
```
### `jobs..after`
A list of job IDs that must complete (successfully or not) before this job will run.
```yaml
jobs:
build:
type: build
params:
platform: ios
notify:
# @info #
after: [build] # This job will run after build completes (whether build succeeds or fails)
# @end #
```
### `jobs..if`
The `if` conditional determines if a job should run. When an `if` condition is met, the job will run. When the condition is not met, the job will be skipped. A skipped job won't have completed successfully and any downstream jobs will not run that have this job in their `needs` list.
```yaml
jobs:
my_job:
# @info #
if: ${{ github.ref_name == 'main' }}
# @end #
```
## Pre-packaged jobs
### `jobs..type`
Specifies the type of pre-packaged job to run. Pre-packaged jobs produce specialized UI according to the type of job on the workflow's detail page.
```yaml
jobs:
my_job:
# @info #
type: build
# @end #
```
Learn about the different pre-packaged jobs below.
#### `build`
Creates an Android or iOS build of your project using [EAS Build](/build/introduction/).
```yaml
jobs:
my_job:
# @info #
type: build
# @end #
params:
platform: ios | android # required
profile: string # optional, default: production
```
This job outputs the following properties:
```json
{
"build_id": string,
"app_build_version": string | null,
"app_identifier": string | null,
"app_version": string | null,
"channel": string | null,
"distribution": "internal" | "store" | null,
"fingerprint_hash": string | null,
"git_commit_hash": string | null,
"platform": "ios" | "android" | null,
"profile": string | null,
"runtime_version": string | null,
"sdk_version": string | null,
"simulator": "true" | "false" | null
}
```
Build jobs can be customized so that you can execute custom commands during the build process. See [Custom builds](/custom-builds/get-started/) for more information.
#### `deploy`
Deploys your application using [EAS Hosting](/eas/hosting/introduction/).
```yaml
jobs:
my_job:
# @info #
type: deploy
# @end #
params:
alias: string # optional
prod: boolean # optional
```
#### `fingerprint`
Calculates fingerprint of Android and iOS builds.
```yaml
jobs:
my_job:
# @info #
type: fingerprint
# @end #
```
> **warning** This job type only supports [CNG (managed)](/workflow/continuous-native-generation/) workflows. If you commit your **android** or **ios** directories, the fingerprint job won't work.
This job outputs the following properties:
```json
{
"android_fingerprint_hash": string,
"ios_fingerprint_hash": string,
}
```
#### `get-build`
Retrieves an existing build from EAS that matches the provided parameters.
```yaml
jobs:
my_job:
# @info #
type: get-build
# @end #
params:
platform: ios | android # optional
profile: string # optional
distribution: store | internal | simulator # optional
channel: string # optional
app_identifier: string # optional
app_build_version: string # optional
app_version: string # optional
git_commit_hash: string # optional
fingerprint_hash: string # optional
sdk_version: string # optional
runtime_version: string # optional
simulator: boolean # optional
```
This job outputs the following properties:
```json
{
"build_id": string,
"app_build_version": string | null,
"app_identifier": string | null,
"app_version": string | null,
"channel": string | null,
"distribution": "internal" | "store" | null,
"fingerprint_hash": string | null,
"git_commit_hash": string | null,
"platform": "ios" | "android" | null,
"profile": string | null,
"runtime_version": string | null,
"sdk_version": string | null,
"simulator": "true" | "false" | null
}
```
#### `submit`
Submits an Android or iOS build to the app store using [EAS Submit](/submit/introduction/). For `environment`, it uses the same environment used to create the build referenced in `build_id`.
> Submission jobs require additional configuration to run within a CI/CD process. See our [Apple App Store CI/CD submission guide](/submit/ios/#submitting-your-app-using-cicd-services) and [Google Play Store CI/CD submission guide](/submit/android/#submitting-your-app-using-cicd-services) for more information.
```yaml
jobs:
my_job:
# @info #
type: submit
# @end #
params:
build_id: string # required
profile: string # optional, default: production
```
#### `update`
Publishes an update using [EAS Update](/eas-update/introduction/).
```yaml
jobs:
my_job:
# @info #
type: update
# @end #
params:
message: string # optional
platform: string # optional - android | ios | all, defaults to all
branch: string # optional
channel: string # optional - cannot be used with branch
```
#### `maestro`
Runs [Maestro](https://maestro.mobile.dev/) tests on a build.
```yaml
maestro:
# @info #
type: maestro
# @end #
environment: production | preview | development # optional, defaults to preview
image: string # optional, defaults to 'default'. See https://docs.expo.dev/build-reference/infrastructure/ for a list of available images.
params:
build_id: string # required
flow_path: string | string[] # required
```
## Custom jobs
Runs custom code and can use built-in EAS functions. Does not require a `type` field.
```yaml
jobs:
my_job:
steps:
# ...
```
### `jobs..runs_on`
Specifies the worker that will execute the job. Available only on custom jobs.
```yaml
jobs:
my_job:
# @info #
runs_on: linux-medium | linux-large |
linux-medium-nested-virtualization |
linux-large-nested-virtualization |
macos-medium | macos-large # optional, defaults to linux-medium
# @end #
```
| Worker | vCPU | Memory (GiB RAM) | SSD (GiB) | Notes |
| ---------------------------------- | ---- | ---------------- | --------- | ------------------------------------ |
| linux-medium | 4 | 16 | 14 | Default worker. |
| linux-large | 8 | 32 | 28 | |
| linux-medium-nested-virtualization | 4 | 16 | 14 | Allows running Android Emulators. |
| linux-large-nested-virtualization | 4 | 16 | 28 | Allows running Android Emulators. |
| macos-medium | 5 | 12 | 85 | Runs iOS jobs, including simulators. |
| macos-large | 10 | 20 | 85 | Runs iOS jobs, including simulators. |
> **Note:** For Android Emulator jobs, you must use a `linux-*-nested-virtualization` worker. For iOS builds and iOS Simulator jobs, you must use a `macos-*` worker.
### `jobs..outputs`
A list of outputs for the job. Job outputs are available to all downstream jobs that depend on this job. Set outputs in a step using the `set-output` function.
In the example below, the `set-output` function sets the output named `test` to the value `hello world` in `job_1`'s `step_1` step. Later in `job_2`, it's accessed in `step_2` using `needs.job_1.outputs.output_1`.
```yaml
jobs:
job_1:
# @info #
outputs:
output_1: ${{ steps.step_1.outputs.test }}
# @end #
steps:
- id: step_1
# @info #
run: set-output test "hello world"
# @end #
job_2:
needs: [job_1]
steps:
# @info #
- id: step_2
run: echo ${{ needs.job_1.outputs.output_1 }}
# @end #
```
### `jobs..steps`
A job contains a sequence of tasks called `steps`. Steps can run commands.
```yaml
jobs:
my_job:
# @info #
steps:
- name: My first step
run: echo "Hello World"
# @end #
```
### `jobs..steps..id`
The `id` property is used to reference the step in the job. Useful for using the step's output in a downstream job.
```yaml
jobs:
my_job:
outputs:
test: ${{ steps.step_1.outputs.test }} # References the output from step_1
steps:
# @info #
- id: step_1
# @end #
run: set-output test "hello world"
```
### `jobs..steps..name`
The human-friendly name of the step, which is displayed in the job's logs. When a step's name is not provided, the `run` command is used as the step name.
```yaml
jobs:
my_job:
steps:
# @info #
- name: My first step
# @end #
run: echo "Hello World"
```
### `jobs..steps..run`
The shell command to run in the step.
```yaml
jobs:
my_job:
steps:
# @info #
- run: echo "Hello World"
# @end #
```
### `jobs..steps..working_directory`
The directory to run the command in. When defined at the step level, it overrides the `jobs..defaults.run.working_directory` setting on the job if it is also defined.
```yaml
jobs:
my_job:
steps:
- uses: eas/checkout
- run: pwd # prints: /home/expo/workingdir/build/my-app
# @info #
working_directory: ./my-app
# @end #
```
### `jobs..steps..uses`
EAS provides a set of built-in reusable functions that you can use in workflow steps. The `uses` keyword is used to specify the function to use. All built-in functions start with the `eas/` prefix.
```yaml
jobs:
my_job:
steps:
# @info #
- uses: eas/checkout
- uses: eas/install_node_modules
- uses: eas/prebuild
- name: List files
run: ls -la
# @end #
```
Below is a list of built-in functions you can use in your workflow steps.
#### `eas/checkout`
Checks out your project source files.
```yaml
jobs:
my_job:
steps:
# @info #
- uses: eas/checkout
# @end #
```
#### `eas/install_node_modules`
Installs node_modules using the package manager (bun, npm, pnpm, or Yarn) detected based on your project. Works with monorepos.
```yaml example.yml
jobs:
my_job:
steps:
- uses: eas/checkout
# @info #
- uses: eas/install_node_modules
# @end #
```
#### `eas/prebuild`
Runs the `expo prebuild` command using the package manager (bun, npm, pnpm, or Yarn) detected based on your project with the command best suited for your build type and build environment.
```yaml
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
# @info #
- uses: eas/prebuild
# @end #
```
```yaml
jobs:
my_job:
steps:
- uses: eas/checkout
- uses: eas/install_node_modules
- uses: eas/resolve_apple_team_id_from_credentials
id: resolve_apple_team_id_from_credentials
# @info #
- uses: eas/prebuild
with:
clean: false
apple_team_id: ${{ steps.resolve_apple_team_id_from_credentials.outputs.apple_team_id }}
# @end #
```
| Property | Type | Description |
| --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `clean` | `boolean` | Optional property defining whether the function should use `--clean` flag when running the command. Defaults to false. |
| `apple_team_id` | `string` | Optional property defining Apple team ID which should be used when doing prebuild. It should be specified for iOS builds using credentials. |
#### `eas/send_slack_message`
Sends a specified message to a configured [Slack webhook URL](https://api.slack.com/messaging/webhooks), which then posts it in the related Slack channel.
The message can be specified as plaintext or as a [Slack Block Kit](https://api.slack.com/block-kit) message.
With both cases, you can reference build job properties and [use other steps outputs](#jobsjob_idoutputs) in the message for dynamic evaluation.
For example, `'Build URL: ${{ eas.job.expoBuildUrl }}'`, `Build finished with status: ${{ steps.run_fastlane.status_text }}`, `Build failed with error: ${{ steps.run_gradle.error_text }}`.
Either "message" or "payload" has to be specified, but not both.
```yaml
jobs:
my_job:
steps:
# @info #
- uses: eas/send_slack_message
# @end #
with:
message: 'This is a message to plain input URL'
slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'
```
| Property | Type | Description |
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message` | `string` | The text of the message you want to send. For example, `'This is the content of the message'`. **Note:** Either `message` or `payload` needs to be provided, but not both. |
| `payload` | `string` | The contents of the message you want to send which are defined using [Slack Block Kit](https://api.slack.com/block-kit) layout. **Note:** Either `message` or `payload` needs to be provided, but not both. |
| `slack_hook_url` | `string` | The previously configured Slack webhook URL, which will post your message to the specified channel. You can provide the plain URL like `slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'`, use EAS secrets like `slack_hook_url: ${{ eas.env.ANOTHER_SLACK_HOOK_URL }}`, or set the `SLACK_HOOK_URL` secret, which will serve as a default webhook URL (in this last case, there is no need to provide the `slack_hook_url` property). |
#### `eas/use_npm_token`
Configures Node package managers (bun, npm, pnpm, or Yarn) for use with private packages, published either to npm or a private registry.
Set `NPM_TOKEN` in your project's secrets, and this function will configure the build environment by creating **.npmrc** with the token.
```yaml example.yml
jobs:
my_job:
name: Install private npm modules
steps:
- uses: eas/checkout
# @info #
- uses: eas/use_npm_token
# @end #
- name: Install dependencies
run: npm install # <---- Can now install private packages
```
## Automating EAS CLI commands
Learn how to automate sequences of EAS CLI commands with EAS Workflows.
If you're using EAS CLI to build, submit, and update your app, you can automate sequences of commands with EAS Workflows. EAS Workflows can build, submit, and update your app, while also running other jobs like Maestro tests, unit tests, custom scripts, and more.
Below you'll find how to set up your project to use EAS Workflows, followed by common examples of EAS CLI commands and how you can run them using EAS Workflows.
## Configure your project
EAS Workflows require a GitHub repository that's linked to your EAS project to run. You can link a GitHub repo to your EAS project with the following steps:
- Navigate to your project's [GitHub settings](https://expo.dev/accounts/%5Baccount%5D/projects/%5BprojectName%5D/github).
- Follow the UI to install the GitHub app.
- Select the GitHub repository that matches the Expo project and connect it.
## Creating builds
You can make a build of your project using EAS CLI with the `eas build` command. To make an iOS build with the `production` build profile, you could run the following EAS CLI command:
```sh
$ eas build --platform ios --profile production
```
To write this command as a workflow, create a workflow file named **.eas/workflows/build-ios-production.yml** at the root of your project.
Inside **build-ios-production.yml**, you can use the following workflow to kick off a job that creates an iOS build with the `production` build profile.
```yaml .eas/workflows/build-ios-production.yml
name: iOS production build
on:
push:
branches: ['main']
jobs:
build_ios:
name: Build iOS
type: build
params:
platform: ios
profile: production
```
Once you have this workflow file, you can kick it off by pushing a commit to the `main` branch, or by running the following EAS CLI command:
```sh
$ eas workflow:run build-ios-production.yml
```
You can provide parameters to make Android builds or use other build profiles. Learn more about build job parameters with the [build job documentation](/eas/workflows/syntax/#build).
## Submitting builds
You can submit your app to the app stores using EAS CLI with the `eas submit` command. To submit an iOS app, you could run the following EAS CLI command:
```sh
$ eas submit --platform ios
```
To write this command as a workflow, create a workflow file named **.eas/workflows/submit-ios.yml** at the root of your project.
Inside **submit-ios.yml**, you can use the following workflow to kick off a job that submits an iOS app.
```yaml .eas/workflows/submit-ios.yml
name: Submit iOS app
on:
push:
branches: ['main']
jobs:
submit_ios:
name: Submit iOS
type: submit
params:
platform: ios
```
Once you have this workflow file, you can kick it off by pushing a commit to the `main` branch, or by running the following EAS CLI command:
```sh
$ eas workflow:run submit-ios.yml
```
You can provide parameters to submit other platforms or use other submit profiles. Learn more about submit job parameters with the [submit job documentation](/eas/workflows/syntax/#submit).
## Publishing updates
You can update your app using EAS CLI with the `eas update` command. To update your app, you could run the following EAS CLI command:
```sh
$ eas update --auto
```
To write this command as a workflow, create a workflow file named **.eas/workflows/publish-update.yml** at the root of your project.
Inside **publish-update.yml**, you can use the following workflow to kick off a job that sends and over-the-air update.
```yaml .eas/workflows/publish-update.yml
name: Publish update
on:
push:
branches: ['*']
jobs:
update:
name: Update
type: update
params:
branch: ${{ github.ref_name || 'test'}}
```
Once you have this workflow file, you can kick it off by pushing a commit to any branch, or by running the following EAS CLI command:
```sh
$ eas workflow:run publish-update.yml
```
You can provide parameters to update specific branches or channels, and configure the update's message. Learn more about update job parameters with the [update job documentation](/eas/workflows/syntax/#update).
## Next step
Workflows are a powerful way to automate your development and release processes. Learn how to create development builds, publish preview updates, and create production builds with the workflows examples guide:
# EAS Build
## EAS Build
EAS Build is a hosted service for building app binaries for your Expo and React Native projects.
**EAS Build** is a hosted service for building app binaries for your Expo and React Native projects.
It makes building your apps for distribution simple and easy to automate by providing defaults that work well for Expo and React Native projects out of the box, and by handling your app signing credentials for you (if you wish). It also makes sharing builds with your team easier than ever with [internal distribution](/build/internal-distribution/) (using ad hoc and/or enterprise "universal" provisioning), deeply integrates with EAS Submit for app store submissions, and has first-class support for the [`expo-updates`](/build/updates/) library.
It's designed to work for any native project, whether or not you use Expo and React Native. It's the fastest way to get from `npx create-expo-app` or `npx @react-native-community/cli@latest init` to app stores.
### Get started
## Create your first build
Learn how to create a build for your app with EAS Build.
EAS Build allows you to build a ready-to-submit binary of your app for the Google Play Store or Apple App Store. In this guide, let's learn how to do that.
Alternatively, if you prefer to install the app directly to your Android device/emulator or install it in the iOS Simulator, we will point you toward resources that explain how to do that.
For a small app, builds for Android and iOS platforms trigger within a few minutes. If you encounter any issues along the way, you can reach out on Discord and Forums.
## Prerequisites
EAS Build is a rapidly evolving service. Before you set out to create a build for your project we recommend consulting the [limitations](/build-reference/limitations) page and the other prerequisites below.
Note: A React Native Android and/or iOS project that you want to build
---
Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide.
Run the following command to create a new project:
```sh
$ npx create-expo-app my-app
```
EAS Build also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools.
---
Note: An Expo user account
---
EAS Build is available to anyone with an Expo account, regardless of whether you pay for EAS or use our Free plan. You can sign up at [https://expo.dev/signup](https://expo.dev/signup).
Paid subscribers get quality improvements such as additional build concurrencies, priority access to minimize the time your builds spend queueing, and increased limits on build timeouts. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing).
---
Step 1:
## Install the latest EAS CLI
EAS CLI is the command-line app that you will use to interact with EAS services from your terminal. To install it, run the command:
```sh
$ npm install -g eas-cli
```
You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version.
> We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation.
Step 2:
## Log in to your Expo account
If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in:
```sh
$ eas login
```
You can check whether you are logged in by running `eas whoami`.
Step 3:
## Configure the project
To configure an Android or an iOS project for EAS Build, run the following command:
```sh
$ eas build:configure
```
To learn more about what happens behind the scenes, see [build configuration process reference](/build-reference/build-configuration).
Additional configuration may be required for some scenarios:
- Does your app code depend on environment variables? [Add them to your build configuration](/build-reference/variables).
- Is your project inside of a monorepo? [Follow these instructions](/build-reference/build-with-monorepos).
- Do you use private npm packages? [Add your npm token](/build-reference/private-npm-packages).
- Does your app depend on specific versions of tools like Node, Yarn, npm, CocoaPods, or Xcode? [Specify these versions in your build configuration](/build/eas-json).
Step 4:
## Run a build
### Build for Android Emulator/device or iOS Simulator
The easiest way to try out EAS Build is to create a build that you can run on your Android device/emulator or iOS Simulator. It's quicker than uploading it to a store, and you don't need store developer membership accounts. If you'd like to try this, read about [creating an installable APK for Android](/build-reference/apk) and [creating a simulator build for iOS](/build-reference/simulators).
### Build for app stores
Before the build process can start for app stores, you will need to have a store developer account and generate or provide app signing credentials.
Whether you have experience with generating app signing credentials or not, EAS CLI does the heavy lifting. You can opt-in for EAS CLI to handle the app signing credentials process. Check out the steps for [Android app signing credentials](#android-app-signing-credentials) or [iOS app signing credentials](#ios-app-signing-credentials) process below for more information.
Note: Google Play Developer membership is required to distribute to the Google Play Store.
---
You can build and sign your app using EAS Build, but you can't upload it to the Google Play Store unless you have a membership, a one-time $25 USD fee.
---
Note: Apple Developer Program membership is required to build for the Apple App Store.
---
If you are going to use EAS Build to create release builds for the Apple App Store, you need access to an account with a $99 USD [Apple Developer Program](https://developer.apple.com/programs) membership.
---
After you have confirmed that you have a Google Play Store or Apple App Store account and decided whether or not EAS CLI should handle app signing credentials, you can proceed with the following set of commands to build for the platform's store:
```sh
$ eas build --platform android
```
```sh
$ eas build --platform ios
```
> You can attach a message to the build by passing `--message` to the build command, for example, `eas build --platform ios --message "Some message"`. The message will appear on the website. It comes in handy when you want to leave a note with the purpose of the build for your team.
Alternatively, you can use `--platform all` option to build for Android and iOS at the same time:
```sh
$ eas build --platform all
```
> If you have released your app to stores previously and have existing [app signing credentials](/app-signing/app-credentials/) that you want to use, [follow these instructions to configure them](/app-signing/existing-credentials).
#### Android app signing credentials
- If you have not yet generated a keystore for your app, you can let EAS CLI take care of that for you by selecting `Generate new keystore`, and then you are done. The keystore is stored securely on EAS servers.
- If you have previously built your app with `expo build:android`, you can use the same credentials here.
- If you want to manually generate your keystore, see the [manual Android credentials guide](/app-signing/local-credentials#android-credentials) for more information.
#### iOS app signing credentials
- If you have not generated a provisioning profile and/or distribution certificate yet, you can let EAS CLI take care of that for you by signing into your Apple Developer Program account and following the prompts.
- If you have already built your app with `expo build:ios`, you can use the same credentials here.
- If you want to rather manually generate your credentials, refer to the [manual iOS credentials guide](/app-signing/local-credentials#ios-credentials) for more information.
Step 5:
## Wait for the build to complete
By default, the `eas build` command will wait for your build to complete, but you can interrupt it if you prefer not to wait. Monitor the progress and read the logs by following the link to the build details page that EAS CLI prompts once the build process gets started. You can also find this page by visiting [your build dashboard](https://expo.dev/builds) or running the following command:
```sh
$ eas build:list
```
If you are a member of an organization and your build is on its behalf, you will find the build details on [the build dashboard for that account](https://expo.dev/accounts/[account]/builds).
> **Did your build fail?** Double check that you followed any applicable instructions in the [configuration step](#3-configure-the-project) and refer to the [troubleshooting guide](/build-reference/troubleshooting) if needed.
Step 6:
## Deploy the build
If you have made it to this step, congratulations! Depending on which path you chose, you now either have a build that is ready to upload to an app store, or you have a build that you can install directly on an Android device/iOS Simulator.
### Distribute your app to an app store
You will only be able to submit to an app store if you built specifically for that purpose. If you created a build for a store, [learn how to submit your app to app stores with EAS Submit](/submit/introduction).
### Install and run the app
You will only be able to install the app directly to your Android device/iOS Simulator if you explicitly built it for that purpose. If you built for app store distribution, you will need to upload to an app store and then install it from there (for example, from Apple's TestFlight app).
To learn how to install the app directly to your Android device/iOS Simulator, navigate to your build details page from [your build dashboard](https://expo.dev/accounts/[account]/builds) and click the "Install" button.
## Next steps
We walked you through the steps to create your first build with EAS Build without going into too much depth on any particular part of the process.
When you are ready to learn more, we recommend proceeding through the **Start Building** section of this documentation to learn about topics such as:
- [Configuration with eas.json](/build/eas-json)
- [Internal distribution](/build/internal-distribution)
- [Updates](/build/updates)
- [Automating submissions](/build/automate-submissions)
- [Triggering builds from CI](/build/building-on-ci)
You may also want to dig through the reference section to learn more about the topics that interest you most, such as:
- [Build webhooks](/eas/webhooks)
- [Build server infrastructure](/build-reference/infrastructure)
- How the [Android](/build-reference/android-builds) and [iOS](/build-reference/ios-builds) build processes work
## Configure EAS Build with eas.json
Learn how a project using EAS services is configured with eas.json.
**eas.json** is the configuration file for EAS CLI and services. It is generated when the [`eas build:configure` command](/build/setup/#configure-the-project) runs for the first time in your project and is located next to **package.json** at the root of your project. Configuration for EAS Build all belongs under the `build` key.
The default configuration for **eas.json** generated in a new project is shown below:
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {}
}
}
```
## Build profiles
A build profile is a named group of configurations that describes the necessary parameters to perform a certain type of build.
The JSON object under the `build` key can contain multiple build profiles, and you can have custom build profile names. In the default configuration, there are three build profiles: `development`, `preview`, and `production`. However, these could have been named `foo`, `bar`, and `baz`.
To run a build with a specific profile, use the command as shown below with a ``:
```sh
$ eas build --profile
```
If you omit the `--profile` flag, EAS CLI will default to using the profile with the name `production` (if it exists).
### Platform-specific and common options
Inside each build profile, you can specify [`android`](/eas/json/#android-specific-options) and [`ios`](/eas/json/#ios-specific-options) fields that contain platform-specific configuration for the build. [Options that are available to both platforms](/eas/json/#common-properties-for-native-platforms) can be provided on the platform-specific configuration object or the root of a profile.
### Sharing configuration between profiles
Build profiles can be extended to other build profile properties using the `extends` option.
For example, in the `preview` profile you might have `"extends": "production"`. This will make the `preview` profile inherit the configuration of the `production` profile.
You can keep chaining profile extensions up to the depth of 5 as long as you avoid making circular dependencies.
## Common use cases
Developers using Expo tools usually end up having three different types of builds: **development**, **preview**, and **production**.
### Development builds
By default, `eas build:configure` will create a `development` profile with `"developmentClient": true`. This indicates that this build depends on [`expo-dev-client`](/develop/development-builds/introduction/). These builds include developer tools, and they are never submitted to an app store.
The `development` profile also defaults to [`"distribution": "internal"`](/build/internal-distribution). This will make it easy to distribute your app directly to physical Android and iOS devices.
You can also configure your development builds to run on the [iOS Simulator](/build-reference/simulators). To do this, use the following configuration for the `development` profile:
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
}
}
}
}
```
> **Note:** For iOS, to create a build for internal distribution and another for the iOS Simulator, you can create a separate development profile for that build. You can give the profile a custom name. For example, `development-simulator`, and use the [iOS Simulator specific configuration](/build-reference/simulators/#configuring-a-profile-to-build-for-simulators) on that profile instead of on `development`. No such configuration is required to run an [Android **.apk** on a device and an Android Emulator](/build-reference/apk) as the same **.apk** will run with both environments.
### Preview builds
These builds don't include developer tools. They are intended to be installed by your team and other stakeholders, to test out the app in production-like circumstances. In this way, they are similar to [production builds](#production-builds). However, they are different from production builds because they are either not signed for distribution on app stores (ad hoc or enterprise provisioning on iOS), or are packaged in a way that is not optimal for store deployment (Android **.apk** is recommended for preview, **.aab** is recommended for Google Play Store).
A minimal `preview` profile example:
```json eas.json
{
"build": {
"preview": {
"distribution": "internal"
}
}
}
```
Similar to [development builds](#development-builds), you can configure a preview build to run on the [iOS Simulator](/build-reference/simulators) or create a variant of your preview profile for that purpose. No such configuration is required to run an [Android **.apk** on a device and an Android Emulator](/build-reference/apk) as the same **.apk** will run with both environments.
### Production builds
These builds are submitted to an app store, for release to the general public or as part of a store-facilitated testing process such as TestFlight.
Production builds must be installed through their respective app stores. They cannot be installed directly on your Android Emulator or device, or iOS Simulator or device. The only exception to this is if you explicitly set `"buildType": "apk"` for Android on your build profile. However, it is recommended to use **.aab** when submitting to stores, as this is the default configuration.
A minimal `production` profile example:
```json eas.json
{
"build": {
"production": {}
}
}
```
### Installing multiple builds of the same app on a single device
It's common to have development and production builds installed simultaneously on the same device. See [Install app variants on the same device](/build-reference/variants/).
## Configuring build tools
Every build depends either implicitly or explicitly on a specific set of versions of related tools that are needed to carry out the build process. These include but are not limited to: Node.js, npm, Yarn, Ruby, Bundler, CocoaPods, Fastlane, Xcode, and Android NDK.
### Selecting build tool versions
Versions for the most common build tools can be set on build profiles with fields corresponding to the names of the tools. For example [`node`](/eas/json/#node):
```json eas.json
{
"build": {
"production": {
"node": "18.18.0"
}
}
}
```
It's common to share build tool configurations between profiles. Use `extends` for that:
```json eas.json
{
"build": {
"production": {
"node": "18.18.0"
},
"preview": {
"extends": "production",
"distribution": "internal"
},
"development": {
"extends": "production",
"developmentClient": true,
"distribution": "internal"
}
}
}
```
### Selecting resource class
A resource class is the virtual machine resources configuration (CPU cores, RAM size) EAS Build provides to your jobs. By default, the resource class is set to `medium`, which is usually sufficient for both small and bigger projects. However, if your project requires a more powerful CPU or bigger memory, or if you want your builds to finish faster, you can switch to `large` workers.
For more details on resources provided to each class, see [`android.resourceClass`](/eas/json/#resourceclass-1) and [`ios.resourceClass`](/eas/json/#resourceclass-2) properties. To run your build on a worker of a specific resource class, configure this property in your build profile:
```json eas.json
{
"build": {
"production": {
"android": {
"resourceClass": "medium"
},
"ios": {
"resourceClass": "large"
},
}
}
}
```
> **Note**: Running jobs on a `large` worker requires a [paid EAS plan](https://expo.dev/accounts/[account]/settings/billing).
### Selecting a base image
The base image for the build job controls the default versions for a variety of dependencies, such as Node.js, Yarn, and CocoaPods. You can override them using the specific named fields as described in the previous section using `resourceClass`. However, the image includes specific versions of tools that can't be explicitly set any other way, such as the operating system version and Xcode version.
If you are building an app with Expo, EAS Build will pick the appropriate image to use with a reasonable set of dependencies for the SDK version that you are building for. Otherwise, it is recommended to see the list of available images on [Build server infrastructure](/build-reference/infrastructure).
### Examples
#### Schema
```json eas.json
{
"cli": {
},
"build": {
"android": {
},
"ios": {
}
},
}
}
```
> You can specify [common properties](/eas/json/##common-properties-for-native-platforms) both in the platform-specific configuration object or at the profile's root. The platform-specific options take precedence over globally-defined ones.
Note: A managed project with several profiles
---
```json eas.json
{
"build": {
"base": {
"node": "12.13.0",
"yarn": "1.22.5",
"env": {
"EXAMPLE_ENV": "example value"
},
"android": {
"image": "default",
"env": {
"PLATFORM": "android"
}
},
"ios": {
"image": "latest",
"env": {
"PLATFORM": "ios"
}
}
},
"development": {
"extends": "base",
"developmentClient": true,
"env": {
"ENVIRONMENT": "development"
},
"android": {
"distribution": "internal",
"withoutCredentials": true
},
"ios": {
"simulator": true
}
},
"staging": {
"extends": "base",
"env": {
"ENVIRONMENT": "staging"
},
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"extends": "base",
"env": {
"ENVIRONMENT": "production"
}
}
}
}
```
---
Note: A bare project with several profiles
---
```json eas.json
{
"build": {
"base": {
"env": {
"EXAMPLE_ENV": "example value"
},
"android": {
"image": "ubuntu-18.04-android-30-ndk-r19c",
"ndk": "21.4.7075529"
},
"ios": {
"image": "latest",
"node": "12.13.0",
"yarn": "1.22.5"
}
},
"development": {
"extends": "base",
"env": {
"ENVIRONMENT": "staging"
},
"android": {
"distribution": "internal",
"withoutCredentials": true,
"gradleCommand": ":app:assembleDebug"
},
"ios": {
"simulator": true,
"buildConfiguration": "Debug"
}
},
"staging": {
"extends": "base",
"env": {
"ENVIRONMENT": "staging"
},
"distribution": "internal",
"android": {
"gradleCommand": ":app:assembleRelease"
}
},
"production": {
"extends": "base",
"env": {
"ENVIRONMENT": "production"
}
}
}
}
```
---
## Environment variables
You can configure environment variables on your build profiles using the `"env"` field. These environment variables will be used to evaluate **app.config.js** locally when you run `eas build`, and they will also be set on the EAS Build builder.
```json eas.json
{
"build": {
"production": {
"node": "16.13.0",
"env": {
"API_URL": "https://company.com/api"
}
},
"preview": {
"extends": "production",
"distribution": "internal",
"env": {
"API_URL": "https://staging.company.com/api"
}
}
}
}
```
The [Environment variables and secrets](/build-reference/variables) reference explains this topic in greater detail, and the [Use EAS Update](/build/updates) guide provides considerations when using this feature alongside `expo-updates`.
## More
## Internal distribution
Learn how EAS Build provides shareable URLs for your builds with your team for internal distribution.
Uploading your app to TestFlight and Google Play beta can be time-consuming (for example, waiting for the build to run through static analysis before becoming available to testers) and limiting (for example, TestFlight can only have one active build at a time). Both Android and iOS provide alternative mechanisms to distribute apps directly to testers, so they can download and install them to physical devices directly from a web browser as soon as the builds are completed.
EAS Build can help you with this by providing shareable URLs for your builds with instructions on how to get them running, so you can share a single URL with a teammate that'll include all the information they need to test the app.
> Installing an app on iOS is a bit trickier than on Android, but it's possible thanks to ad hoc and enterprise provisioning profiles. We'll talk more about this later in this doc.
## Overview of distribution mechanisms
Both Android and iOS offer ways to install a build of your app directly on a device. This gives you full control of putting specific builds on devices, allowing you to iterate quickly and have multiple builds of your app available for review at the same time.
### Internal distribution
This section provides an overview of mechanisms for internal distribution on both platforms.
Note: Android: Build and distribute an APK
---
To share your app to Android devices, you must build an APK (Android application package file) of your project. APKs can be installed directly to an Android device over USB, by downloading the file over the web or through an email or chat app, once the user accepts the security warning for installing an app that has not gone through Play Store review. AAB (Android app bundle) binaries of your app must be distributed through the Play Store.
---
Note: iOS: Ad Hoc distribution
---
Apple offers [ad hoc provisioning profiles](https://help.apple.com/xcode/mac/current/#/dev7ccaf4d3c) to distribute your app to test devices once they have been registered
to your Apple Developer account. This method requires a paid Apple Developer account and that account will only be able to use this method to distribute to at most 100 iPhones per year.
You will need to know the UDID (Unique Device Identifier) of each device that will install your app, which may be challenging if you try to share with someone who is not a developer. Adding a new device will require a rebuild of your app.
Setting up Ad Hoc certificates correctly can be intimidating if you haven't done it before and tedious even if you have. If you're using [EAS Build](#internal-distribution-with-eas-build), which is optimized for Expo and React Native projects, we'll handle the time-consuming parts of setting up Ad Hoc credentials for you.
---
Note: iOS: Enterprise distribution
---
If your app is only intended for internal use by employees of a large organization and cannot be distributed through the App Store, you should use Enterprise distribution. Unlike with Ad Hoc Distribution, the number of devices that can install your app is unlimited, and you do not need to manage each device's UDID. Often these apps will be distributed to end users through a mobile device management (MDM) solution. Enterprise Distribution requires membership in the [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/). Organizations joining the Enterprise Program must meet additional requirements beyond what is required for App Store distribution.
---
### Google Play/App Store testing releases
Note: Android: Google Play beta
---
Before a complete public release, [Google Play beta](https://support.google.com/googleplay/android-developer/answer/9845334?visit_id=638740965629093187-3840249980&rd=1) is another option to distribute your app to testers. You can set up either an internal, closed, or open test track and control who has access to the app.
Each test track has its own requirements. For the internal track, you can only invite up to 100 testers. Both closed and open tracks support larger groups of testers. In closed tracks, you need to invite testers, while in open tracks, anyone can join your program.
To use Google Play beta, you need to upload your app as an AAB (Android App Bundle) to the Google Play Console, set up a test track, and invite users via email or a shareable link. Testers can install the app through the Play Store, and you can collect feedback and crash reports directly from the Google Play Console.
---
Note: iOS: TestFlight
---
TestFlight is another option to distribute your app to iOS devices. TestFlight also requires a paid Apple Developer account. TestFlight's internal testing option allows you to create test groups that include up to 100 members of your Apple Developer account team, who then download the app through the TestFlight app. Some teams prefer TestFlight because it doesn't require a new build to add new testers, and apps stay updated automatically.
TestFlight also includes an external testing option that allows you to share your app with up to 10,000 users via an email or a public link.
Both internal and external test distribution in TestFlight require you to [upload your app](/submit/ios) to App Store Connect and wait for the automated review before you can share a build. However, external test builds will need to go through a more formal App Store review (which is distinct from the review that your app must undergo before production release) before being distributed.
---
## Internal distribution with EAS Build
Setting up an internal distribution build only takes a few minutes with EAS Build and provides a streamlined way to share your app with your team and testers for feedback.
The setup requires setting `"distribution": "internal"` to the desired build profile in your **eas.json** file. This configuration automatically generates **.apk** files for Android and **.ipa** files for iOS so that they can be installed on devices directly and do not require a development server to run the app.
For Android, EAS Build will generate a new Android keystore for signing the APK, or it will use an existing one if the package name is the same as your [development build](/develop/development-builds/introduction/). For iOS, EAS Build will generate an ad hoc provisioning profile that explicitly allows devices to run that app based on that device's UDID. This can be done using `eas device:create` when creating a new build.
See the tutorial on Internal distribution with EAS Build below for more information on how to configure, create, and install a build:
### Automation on CI (optional)
It's possible to run internal distribution builds non-interactively in CI using the `--non-interactive` flag. However, if you are using ad hoc provisioning on iOS you will not be able to add new devices to your provisioning profile when using this flag. After registering a device through `eas device:create`, you need to run `eas build` interactively and authenticate with Apple in order for EAS to add the device to your provisioning profile. [Learn more about triggering builds from CI](/build/building-on-ci).
### Managing devices
You can see any devices registered via `eas device:create` by running:
```sh
$ eas device:list
```
Devices registered with Expo for ad hoc provisioning will appear on your Apple Developer Portal after they are used to generate a provisioning profile for a new internal build with EAS Build or to [resign an existing build](/app-signing/app-credentials/#re-signing-new-credentials) with `eas build:resign`.
#### Remove devices
If a device is no longer in use, it can be removed from this list by running:
```sh
optionally disable them on the Apple Developer Portal
$ eas device:delete
```
This command will also prompt you to disable the device on the Apple Developer Portal. Disabled
devices still count against [Apple's limit of 100
devices](https://developer.apple.com/support/account/#:~:text=Resetting%20your%20device%20list%20annually)
for ad hoc distribution per app.
#### Rename devices
Devices added via the website URL/QR code will default to displaying their UDID when selecting them for an EAS Build. You can assign friendly names to your devices with the following command:
```sh
$ eas device:rename
```
## Automate submissions
Learn how to enable automatic submissions with EAS Build.
Many mobile deployment processes eventually evolve to the point where the app is automatically submitted to the respective store once an appropriate build is completed. This saves developers from having to wait around for the build to complete, avoids a bit of manual work, and eliminates the need to coordinate providing app store credentials to the team.
EAS Build gives you automatic submissions out of the box with the `--auto-submit` flag. This flag tells EAS Build to pass the build along to EAS Submit with the appropriate submission profile upon completion. Refer to the [EAS Submit documentation](/submit/introduction) for more information on how to set up and configure submissions.
When you run `eas build --auto-submit` you will be provided with a link to a submission details page, where you can track the progress of the submission. You can also find this page at any time on the [submissions dashboard for your project](https://expo.dev/accounts/[account]/projects/[project]/submissions), and it is linked from your build details page.
### Selecting a submission profile
By default, `--auto-submit` will try to use a submission profile with the same name as the selected build profile. If this does not exist, or if you prefer to use a different profile, you can use `--auto-submit-with-profile=` instead.
### Build profile environment variables and submissions
When running `eas build --profile --auto-submit`, the project's **app.config.js** will be evaluated using any environment variables associated with the build profile ``. For example, suppose we ran `eas build -p ios --profile production --auto-submit` with the following configuration:
```json eas.json
{
"build": {
"production": {
"env": {
"APP_ENV": "production"
}
},
"development": {
"env": {
"APP_ENV": "development"
}
}
}
}
```
```js app.config.js
export default () => {
return {
name: process.env.APP_ENV === 'production' ? 'My App' : 'My App (DEV)',
ios: {
bundleIdentifier: process.env.APP_ENV === 'production' ? 'com.my.app' : 'com.my.app-dev',
},
// ... other config here
};
};
```
The `APP_ENV` variable from the `production` profile will be used when evaluating **app.config.js** for the submission, and therefore the name will be 'My App' and the bundle identifier will be 'com.my.app'.
## Using EAS Update
Learn how to use EAS Update with EAS Build.
EAS Build includes some special benefits for [`expo-updates`](/versions/latest/sdk/updates/) library. In particular, you can configure the [`channel`](/eas-update/how-it-works/#distributing-builds) property in **eas.json** and EAS Build will take care of updating it in your native project at build time.
This document covers concerns specific to using `expo-updates` library with EAS Build. For more general information about configuring the library with EAS Update, see [Getting started with EAS Update ](/eas-update/getting-started/).
## Setting the channel for a build profile
Each [build profile](/build/eas-json/#build-profiles) can be assigned to a channel, so updates for builds produced for a given profile will pull only those releases that are published to its channel.
The following example demonstrates how you might use the `"production"` channel for production builds, and the `"staging"` channel for test builds distributed with [internal distribution](/build/internal-distribution/).
```json eas.json
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "staging",
"distribution": "internal"
}
}
}
```
## Binary compatibility and runtime versions
Your native runtime may change on each build, depending on whether you modify the code in a way that changes the API contract with JavaScript. If you publish a JavaScript bundle to a binary with an incompatible native runtime (for example, a function that the JavaScript bundle expects to exist does not exist) then your app may not work as expected, or it may crash.
We recommend using a different [runtime version](/distribution/runtime-versions/) for each binary version of your app. Any time you change the native runtime (in managed apps, this happens when you add or remove a native library, or modify **app.json**), you should increment the runtime version.
## Previewing updates in development builds
Updates published with the `runtimeVersion` field can't be loaded in Expo Go. Instead, you should use [`expo-dev-client`](/versions/latest/sdk/dev-client/) to create a development build.
## Environment variables and `eas update`
Environment variables set on the `env` field in build profiles are not available when you run `eas update`. Learn more about using [environment variables with EAS Update](/eas-update/environment-variables).
## Trigger builds from CI
Learn how to trigger builds on EAS for your app from a CI environment such as GitHub Action and more.
This document outlines how to trigger builds on EAS for your app from a CI environment such as GitHub Actions, Travis CI, and more.
## Prerequisites
### Run a successful build from your local machine
To trigger EAS builds from a CI environment, your app needs to be set up to use EAS Build in non-interactive mode. To do this, go through the EAS Build initialization steps and run a successful build from your local terminal for each platform you would like to support on CI. This way, the `eas build` command can prompt for any additional configuration it needs, and then that configuration will be available for future non-interactive runs on CI.
Running a build locally will accomplish the following critical configuration steps:
- Initialize the project on EAS by generating a `projectId`.
- Add an **eas.json** file defining your build profiles.
- Populates critical app config properties for native builds, such as `android.packageName` and `ios.bundleIdentifier`.
- Ensure build credentials are created, including Android keystores and iOS distribution certs and provisioning profiles.
Run `eas build -p [all|android|ios]` and verify that your builds for each platform complete successfully. Then, continue with the below steps for implementing EAS Build on CI.
If you haven't done this yet, see the [Create your first build](/build/setup/) guide and return here when you're ready.
## Using EAS Workflows
[EAS Workflows](/eas/workflows/get-started) is a service from Expo that allows you to run builds, and many other types of jobs, on EAS. You can use EAS Workflows to automate your development and release processes, like creating development builds or automatically building and submitting to the app stores.
To create a build with EAS Workflows, start by adding the following code in **.eas/workflows/build.yml**:
```yaml
name: Build
on:
push:
branches:
- main
jobs:
build_android:
name: Build Android App
type: build
params:
platform: android
build_ios:
name: Build iOS App
type: build
params:
platform: ios
```
When a commit is pushed to the main branch, this workflow will create Android and iOS builds. You can learn how to modify this workflow and sequence other types of jobs in the [EAS Workflows documentation](/eas/workflows/get-started).
## Configuring your app for other CI services
{/* ### Make EAS CLI available in your CI environment */}
{/* To interact with the EAS API, we need to install EAS CLI. You can use an environment with this library preinstalled, or you can add it to the project as a development dependency. */}
{/* The latter is the easiest way, but may increase the installation time. */}
{/* For vendors that charge you per minute, it might be worth creating a prebuilt environment. */}
{/* To install EAS CLI in your project, run: */}
{/* ```sh */}
{/* npm install --save-dev eas-cli */}
{/* ``` */}
{/* > **info** Make sure to update this dependency frequently to stay up to date with the EAS API interface. */}
### Provide a personal access token to authenticate with your Expo account on CI
Next, we need to ensure that we can authenticate ourselves on CI as the owner of the app. This is possible by storing a personal access token in the `EXPO_TOKEN` environment variable in the CI settings.
See [personal access tokens](/accounts/programmatic-access/#personal-access-tokens) to learn how to create access tokens.
### (Optional) Provide an ASC API Token for your Apple Team
In the event your iOS credentials need to be repaired, we will need an ASC API key to authenticate ourselves to Apple in CI. A common case is when your provisioning profile needs to be re-signed.
You will need to create an [API Key](https://expo.fyi/creating-asc-api-key). Next, you will need to gather information about your [Apple Team](https://expo.fyi/apple-team).
Using the information you've gathered, pass it into the build command through environment variables. You will need to pass the following:
- `EXPO_ASC_API_KEY_PATH`: The path to your ASC API Key **.p8** file. For example, **/path/to/key/AuthKey_SFB993FB5F.p8**.
- `EXPO_ASC_KEY_ID`: The key ID of your ASC API Key. For example, `SFB993FB5F`.
- `EXPO_ASC_ISSUER_ID`: The issuer ID of your ASC API Key. For example, `f9675cff-f45d-4116-bd2c-2372142cee09`.
- `EXPO_APPLE_TEAM_ID`: Your Apple Team ID. For example, `77KQ969CHE`.
- `EXPO_APPLE_TEAM_TYPE`: Your Apple Team Type. Valid types are `IN_HOUSE`, `COMPANY_OR_ORGANIZATION`, or `INDIVIDUAL`.
### Trigger new builds
Now that we're authenticated with Expo CLI, we can create the build step.
To trigger new builds, we will add this script to our configuration:
```sh
$ npx eas-cli build --platform all --non-interactive --no-wait
```
This will trigger a new build on EAS. A URL will be printed, linking to the build's progress in the EAS dashboard.
> **info** The `--no-wait` flag exits the step once the build has been triggered. You are not billed for CI execution time while EAS performs the build. However, your CI will report that the build job is passing only if triggering EAS Build is successful.
>
> If you need to add another CI step to run once the build is complete, remove this flag.
Note: Travis CI
---
Add the following code snippet in **.travis.yml** at the root of your project repository.
```yaml travis.yml
language: node_js
node_js:
- node
- lts/*
cache:
directories:
- ~/.npm
before_script:
- npm install -g npm@latest
jobs:
include:
- stage: build
node_js: lts/*
script:
- npm ci
- npx eas-cli build --platform all --non-interactive --no-wait
```
---
Note: GitLab CI
---
Add the following code snippet in **.gitlab-ci.yml** at the root of your project repository.
```yaml .gitlab-ci.yml
image: node:alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm
# or with Yarn:
#- .yarn
stages:
- build
before_script:
- npm ci --cache .npm
# or with Yarn:
#- yarn install --cache-folder .yarn
eas-build:
stage: build
script:
- apk add --no-cache bash
- npx eas-cli build --platform all --non-interactive --no-wait
```
---
Note: Bitbucket Pipelines
---
Add the following code snippet in **bitbucket-pipelines.yml** at the root of your project repository.
```yaml bitbucket-pipelines.yml
image: node:alpine
definitions:
caches:
npm: ~/.npm
pipelines:
default:
- step:
name: Build app
deployment: test
caches:
- npm
script:
- apk add --no-cache bash
- npm ci
- npx eas-cli build --platform all --non-interactive --no-wait
```
---
Note: CircleCI
---
Add the following code snippet in **circleci/config.yml** at the root of your project repository.
```yaml .circleci/config.yml
version: 2.1
executors:
default:
docker:
- image: cimg/node:lts
working_directory: ~/my-app
jobs:
eas_build:
executor: default
steps:
- checkout
- run:
name: Install dependencies
command: npm ci
- run:
name: Trigger build
command: npx eas-cli build --platform all --non-interactive --no-wait
workflows:
build_app:
jobs:
- eas_build:
filters:
branches:
only: master
```
---
Note: GitHub Actions
---
Add the following code snippet in **.github/workflows/eas-build.yml** at the root of your project repository.
```yaml .github/workflows/eas-build.yml
name: EAS Build
on:
workflow_dispatch:
push:
branches:
- main
jobs:
build:
name: Install and build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18.x
cache: npm
- name: Setup Expo and EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies
run: npm ci
- name: Build on EAS
run: eas build --platform all --non-interactive --no-wait
```
---
## Trigger builds from the Expo GitHub App
Learn how to trigger builds on EAS for your app using the Expo GitHub App.
This guide explains how to trigger builds directly from your GitHub repository using the Expo GitHub App.
## Prerequisites
### Set the `image` field in your eas.json
For the build profiles you want to use with GitHub, specify an [`image`](/eas/json/#image) to use for the native platform in **eas.json**.
Use the `latest` image if your project's configuration does not rely on a specific [build image](/build-reference/infrastructure/). For example:
```json eas.json
{
"build": {
"production": {
"android": {
"image": "latest"
},
"ios": {
"image": "latest"
}
}
}
}
```
### Run a successful build from your local machine
To trigger EAS builds from a GitHub repo, you'll need to configure your project for EAS Build and successfully run a build from your computer for each platform that you'd like to support on GitHub.
If you haven't successfully run `eas build -p [all|ios|android]` yet, see [Create your first build](/build/setup/) for more information. Once you have, continue with the steps in this guide.
The following must also be true:
- An Expo user in the organization must have a linked GitHub user with access to the target repository. Check [User settings > Connections](https://expo.dev/settings#connections) and verify that your GitHub user account is linked.
- You must accept the permissions requested by the [Expo GitHub app](https://github.com/settings/installations).
## Configure your app for GitHub
### Link your GitHub repository to your Expo project
Visit your project's [GitHub settings](https://expo.dev/accounts/[account]/projects/[projectName]/github).
Install the Expo GitHub App on your GitHub account.
> **Note:** You must have [Owner or Admin access](/accounts/account-types/#manage-access) of the Expo account to install the app.
Then, link the GitHub repository to your Expo project.
> **Note:** You can only link [GitHub organization repositories](https://docs.github.com/en/organizations) to Expo organizations.
To add a repository from a different GitHub account, click the **Add new account** option in the account selector dropdown.
### Configure your repository settings
Before you run a build, the Expo GitHub App needs to know where to find the source code for your project. If your Expo
project source code is in the root of your repository, then you don't need to do anything. If your
Expo project source code is in a subdirectory, then you'll need to configure "Base directory"
settings for your repository on your project's [GitHub settings
page](https://expo.dev/accounts/[account]/projects/[projectName]/github).
## Trigger a build from GitHub
Once you have configured your app for GitHub, you can trigger a build from GitHub by using the UI on your project's build list page or by labels on your GitHub PRs.
### Build using the Expo website
Visit your project's [build list
page](https://expo.dev/accounts/[account]/projects/[projectName]/builds) and click the "Build from
GitHub" button. You'll be prompted to select a Git ref (branch/commit/tag), a platform to build
for, and the build profile to apply to it.
You can also specify a base directory for this specific build. That will not change the global
settings for this project.
### Build using GitHub PR labels
You can trigger a build from a GitHub PR by adding a label to the PR. The label must be in the form
of `eas-build-[platform]:[profile]` where `[platform]` is either `android`, `ios`, or `all` and
`[profile]` is the name of a build profile specified in your **eas.json** file. If you don't specify
a build platform, it will default to `all`. If you don't specify
a build profile, it will default to `production`.
For example, if
you want to trigger a production build for Android, add the label `eas-build-android` to the PR.
The build will be triggered for the latest commit on the PR's base branch. You can view the status
of the build in the PR's checks. A link to the build will be available in the check's details.
### Build automatically when code is pushed to repository
You can take your build automation further by automatically building your Expo project when you push code to GitHub.
#### Set up build triggers
You can set up build triggers to configure when EAS builds your app from GitHub. We allow you to build when pushing to a branch, pull request, and Git tag.
Open your Expo project in the dashboard. To create a build trigger, scroll down to the **Build
triggers** section of the project GitHub settings page and click **New Build Trigger**.
When you click **New Build Trigger**, you will be presented with a form to configure how this build should run.
These patterns can include wildcards represented by asterisks (`*`), which can match any character and number of characters inside the pattern. For example, `releases/*` can match `releases/`, `release/1234`, `release/genesis`, and so on. If you specify the pattern as a sole asterisk (`*`), all branches/tags will be matched.
You can also configure triggers for specific platforms and build profiles. If you select multiple
platforms, a separate trigger will be made for each.
When you push to a branch or tag, you can find the builds by looking at a commit's **Checks** section.
For pull requests, you can configure a **target branch pattern**. This is the destination branch of the pull request you want to build. The same rules apply for wildcards here as well.
When you push to a pull request with a source and target branch matching this trigger, you'll find
these builds in the checks section of the pull request:
> **Note:** To trigger builds from a pull request, the pull request's author must be a collaborator
> on the GitHub repository. If you want to build pull requests from external contributors, [apply a PR Label](#build-using-github-pr-labels).
#### Manage build triggers
On your project's GitHub settings page in the Expo dashboard, you can click the options button to the right of
a build trigger row to disable, edit, or delete the trigger.
You can also run a GitHub build with the parameters from the trigger manually. This will not count towards your automatic build trigger record.
#### Automatic app stores submission with EAS Submit
Once your build completes, you can automatically submit your app to the app stores using EAS Submit. This feature streamlines the process, reducing the manual steps required to publish your app.
To enable automatic submission, you need to configure your build triggers to include submission as part of the build process. Here's how you can set it up:
- Navigate to your project's GitHub settings page on the Expo dashboard.
- Find the build trigger you want to modify, and click the options button.
- Select **Edit trigger** and in the dialog that appears, check the option **Submit to store after build**.
- Save your changes.
Once enabled, every time a build is triggered from this configuration, it will automatically be submitted to the app stores you have configured in your **eas.json** under the `submit` field.
> **Note:** Ensure that your **eas.json** is properly configured for submission, including specifying the correct app store's credentials and submission profile. For more information, see the [EAS Submit](/submit/eas-json/).
### Troubleshooting
- When things go wrong, we will comment on the commit we attempted to build with some error information. We also show the latest result in the build triggers UI, which includes error information when you hover the **Error** tag.
- Double check everything in the [Prerequisites](#prerequisites) section is true when trying to build.
- Confirm that your base directory is accurate if you're using a monorepo setup.
- Is your build profile correct? If a matching profile can't be found in **eas.json**, the build will not dispatch.
## Expo Orbit
Accelerate your development workflow with one-click build and update launches and simulator management.
[Expo Orbit](https://expo.dev/orbit) for macOS and Windows enables faster to install and launch builds or updates from EAS, local files, or run Snack projects, on simulators and physical devices.
## Why Orbit
Before Orbit, installing builds or updates from EAS (on Android and iOS physical devices or emulator/simulator) or running Snack projects on simulators was manual. You had to run `eas build:run` command and select a build for your chosen device or download the archive and then drag and drop it to the simulator (in the case of iOS). Also, for Snack projects, additional steps included installing Expo Go on the virtual device, logging in, and then selecting the Snack from the list. Orbit makes all of these steps as seamless as possible.
## Highlights
- List and launch simulators, including running Android emulators without audio.
- Install and launch builds from EAS on simulators and real devices in one click.
- [Install and open updates from EAS](/review/with-orbit/) on Android Emulators or iOS Simulators.
- Launch Snack projects in your simulators in one click.
- Install and launch apps from local files using Finder or drag and drop a file into the menu bar app. Orbit supports any Android **.apk**, iOS Simulator compatible **.app**, or ad hoc signed apps.
- See pinned projects from your [EAS dashboard](https://expo.dev) and quickly launch your latest builds.
## Installation
> **info** Orbit relies on the Android SDK on both macOS and Windows and `xcrun` for device management only on macOS, which requires setting up both [Android Studio](/workflow/android-studio-emulator/) and [Xcode](/workflow/ios-simulator/).
For macOS:
You can download Orbit with Homebrew for macOS, or directly from the [GitHub releases](https://github.com/expo/orbit/releases).
```sh
$ brew install expo-orbit
```
If you want Orbit to start when you log in automatically, click on the Orbit icon in the menu bar, then **Settings** and select the **Launch on Login** option.
For Windows:
> **Note**: Orbit for Windows is in preview and is only compatible with x64 and x86 machines. Compatibility for other architectures will be added in the future.
You can download Orbit for Windows directly from the [GitHub releases](https://github.com/expo/orbit/releases).
# App signing
## App credentials
Learn about what app credentials Android and iOS require.
Expo automates the process of signing your app for Android and iOS, but in both cases, you can choose to provide your overrides. [EAS Build](/build/introduction) can generate signed or unsigned applications, but to distribute your application through the stores, it **must** be a signed application.
On this page, you'll learn about the credentials that each platform requires. If you're curious about how we store your credentials on our end, take a look at our [security documentation](/app-signing/security).
## Android
Google requires all Android apps to be digitally signed with a certificate before they are installed on a device or updated. Usually,
a private key and its public certificate are stored in a keystore. In the past, APKs uploaded to the store were required to be signed with
the **app signing certificate** (a certificate that will be attached to the app in the Play Store), and if the keystore was lost there was no way to
recover or reset it. Now, you can opt-in to App Signing by Google Play and simply upload an APK signed with an **upload certificate**, and Google Play will automatically replace it with the **app signing certificate**. Both the old method (app signing certificate) and new method (upload certificate) are essentially the same mechanisms, but using the new method, if your upload keystore is lost or compromised, you can contact the Google Play support team to reset the key.
From the Expo build process's perspective, there is no difference between whether an app is signed with an **upload certificate** or an **app signing key**. Either way, `eas build` will generate an **.apk** or **.aab** signed with the keystore currently associated with your application. If you want to generate an upload keystore manually, you can do that the same way you created your original keystore.
See [Android's documentation](https://developer.android.com/studio/publish/app-signing) to find more information about this process.
### App signing by Google Play
When you [upload your first release to Google Play](https://expo.fyi/first-android-submission) you will see a notice about "App signing by Google Play" and "Google is protecting your app signing key". This is the default behavior and requires no action on your behalf except to press "Continue".
If you currently manage your app signing key and want Google to manage it for you, see [Use app signing by Google Play](https://support.google.com/googleplay/android-developer/answer/9842756).
Note: Lost your keystore? Learn how to reset your upload key on Google Play
---
To sync your Expo keystore with Google, follow these steps:
#### Download credentials
In a terminal window:
1. Run `eas credentials` command.
2. Select `Android` for the platform and the profile whose credentials you wish to download.
3. Select the option `credentials.json: Upload/Download credentials between EAS servers and your local json`.
4. Select `Download credentials from EAS to credentials.json`.
Your application's keystore should be kept private. **Under no circumstances should you check it into your repository.** Debug keystores are the only exception because we don't use them for uploading apps to the Google Play Store.
#### Export keystore to `pem` format
Once you have downloaded your credentials and the keystore, export it to the `pem` format so that you can submit it to Google:
1. Find the key alias in your **credentials.json** file under the `keyAlias` key.
2. Use `keytool` to export the certificate:
```sh
$keytool -export -rfc -alias alias_from_step_1 -file certificate_for_google.pem -keystore ./path/to/keystore.jks
```
#### Contact Google support
Contact Google Support and request them to change your key using [this support form](https://support.google.com/googleplay/android-developer/contact/key). While filling out the form, attach the `pem` file exported from the keystore.
Once Google updates this on your account, builds created through `eas build` will be correctly signed as expected by the Google Play Store. Note that Google will set the validity start date of the new upload certificate to 72 hours in the future so you'll have to wait before your first submission after performing this process.
---
## iOS
The 3 primary iOS credentials, all of which are associated with your Apple Developer account, are:
- Distribution Certificate
- Provisioning Profiles
- Push Notification Keys
Whether you let EAS handle all your credentials, or you handle them yourself, it can be valuable to understand what each of these credentials means, when and where they're used, and what happens when they expire or are revoked. You can inspect and manage all your credentials with EAS CLI by running `eas credentials`.
### Distribution certificate
The distribution certificate is all about you, the developer, and not about any particular app. You may only have one distribution certificate associated with your Apple Developer account. This certificate will be used for all of your apps. If this certificate expires, your apps in production will not be affected. However, you will need to generate a new certificate if you want to upload new apps to the App Store or update any of your existing apps. Deleting a distribution certificate has no effect on any apps already on the App Store. You can clear the distribution certificate Expo currently has stored for your app the next time you build by running `eas credentials` and following the prompts.
### Push Notification keys
Apple Push Notification Keys (often abbreviated as APN keys) allow the associated apps to send and receive push notifications.
You can have a maximum of 2 APN keys associated with your Apple Developer account, and a single key can be used with any number of apps. If you revoke an APN key, all apps that rely on that key will no longer be able to send or receive push notifications until you upload a new key to replace it. Uploading a new APN key **will not** change your users' [Expo Push Tokens](/versions/latest/sdk/notifications#notificationsgetexpopushtokenasync). Push notification keys do not expire. You can clear the APN key Expo currently has stored for your app by running `eas credentials` and following the prompts.
> APN keys created by Expo can be downloaded on the [Expo website](https://expo.dev/accounts/[account]/settings/credentials).
### Provisioning profiles
Each profile is app-specific, meaning you will have a provisioning profile for every app you submit to the App Store. These provisioning profiles are associated with your distribution certificate, so if that is revoked or expired, you'll need to regenerate the app's provisioning profile, as well. Similar to the distribution certificate, revoking your app's provisioning profile will not have any effect on apps already on the App Store.
Provisioning profiles expire after 12 months, but this won't affect apps in production. You will just need to create a new one the next time you build your app by running `eas build -p ios`, or manually with `eas credentials`.
### Summary
| Credential | Limit Per Account | App-specific? | Can be revoked with no production side effects? | Used at |
| ------------------------ | ----------------- | ------------- | ----------------------------------------------- | ---------- |
| Distribution Certificate | 2 | | | Build time |
| Push Notification Key | 2 | | | Run time |
| Provisioning Profile | Unlimited | | | Build time |
### Clearing credentials
When you use the `eas credentials` command to delete your credentials, this only removes those credentials from Expo's servers. **It does not delete the credentials from Apple's perspective**. This means that to fully delete your credentials (for example, if you want a new push notification key, however, you already have two), you'll need to do so from the [Apple Developer Console](https://developer.apple.com/account/resources/certificates/list).
### Re-signing new credentials
You can use `eas build:resign` to codesign an existing **.ipa** for iOS to a new ad hoc provisioning profile. This helps reduce time when distributing internally — for example, if you want to add a new test device to an existing build, you can use this command to update the provisioning profile to include the device without rebuilding the entire app from scratch.
Running the command will ask you to select a build that you want to re-sign. For example, running the command in an example project shows an available build:
After selecting the build, follow the steps to log in to your Apple Developer account. When prompted **Show devices and ask me again**, you can select a new provisioning profile.
Select a new device, and the command will run the EAS Build again. Note that the build triggered this time reuses the application artifact from the selected build and codesigns it with the new provisioning profile. Once this process is complete, you can use this new build link to install the **.ipa** on the iOS device added to the provisioning profile.
## Using automatically managed credentials
Learn how to automatically manage your app credentials with EAS.
For your app to be distributed in an app store, it needs to be digitally signed with credentials such as a keystore or a distribution certificate. This certifies the source of the app and ensures that it can't be tampered with. Other credentials, such as your FCM API Key and Apple Push Key are needed to send push notifications, but they are not involved in app signing.
That's all that you need to know about any of this to build an app with EAS Build, but if you would like to learn more you can refer to the [App Signing](/app-signing/app-credentials) guide.
Read on to learn how EAS can automatically manage credentials for you and your team.
## Generating app signing credentials
When you run `eas build`, you will be prompted to generate credentials if you have not done so already. Follow the simple instructions to generate your credentials. Where needed, they will be stored on EAS servers. On subsequent builds of your app, these credentials will be re-used unless you specify otherwise.
Generating your iOS credentials (distribution certificate, provisioning profile, and push key) requires you to sign in with an [Apple Developer Program](https://developer.apple.com/programs) membership.
> If you have any security concerns about EAS managing your credentials or about logging in to your Apple Developer account through EAS CLI, see [Security](/app-signing/security) guide. If that does not satisfy your concerns, you can reach out to [secure@expo.dev](mailto:secure@expo.dev) for more information, or use [local credentials](/app-signing/local-credentials/) instead.
### Push notification credentials
#### Android
The Android push notification credentials setup for EAS Build requires configuring your app with FCM. Run `eas credentials`, select `Android`, then `Push Notifications: Manage your FCM Api Key`, and then choose the appropriate option to set up the key.
#### iOS
If you haven't set up your Push Notifications key yet, EAS CLI will ask you to set it up during the next `eas build` run.
You can also set up the Push Notifications key with the `eas credentials` command. Run it, select `iOS`, then `Push Notifications: Manage your Apple Push Notifications Key`, and then choose the appropriate option to set up the key.
## Sharing credentials with your team
If you collaborate on your project with other developers, it is often useful to give them access to perform builds on their own. [Ensure that your project is configured for collaboration](/accounts/account-types/#organizations) and any teammates that you have added through your [Expo dashboard](https://expo.dev/) will be able to run `eas build` seamlessly, provided that they have sufficient permissions.
After you have generated your iOS credentials, it's no longer necessary to have access to the Apple Developer team to start a build. This means that your collaborators can start new iOS builds with only their Expo accounts.
## Inspecting credentials configuration
You can view your currently configured app signing credentials by running `eas credentials`. This command also lets you remove and modify credentials, should you need to make any changes. Typically this is not necessary, but you may want to use it if you want to [sync your credentials to your local machine to run a build locally](/app-signing/syncing-credentials/) or [migrate existing credentials to be automatically managed](/app-signing/existing-credentials/).
## Using local credentials
Learn how to configure and use local credentials when using EAS.
You can usually get away with not being a code signing expert by [letting EAS handle it for you](/app-signing/managed-credentials/). However, there are cases where some users might want to manage their project keystore, certificates and profiles on their own.
If you would like to manage your own app signing credentials, you can use **credentials.json** to give EAS Build relative paths to the credentials on your local file system and their associated passwords to use them to sign your builds.
## credentials.json
If you opt-in to local credentials configuration, you'll need to create a **credentials.json** file at the root of your project, and it should look something like this:
```json credentials.json
{
"android": {
"keystore": {
"keystorePath": "android/keystores/release.keystore",
"keystorePassword": "paofohlooZ9e",
"keyAlias": "keyalias",
"keyPassword": "aew1Geuthoev"
}
},
"ios": {
"provisioningProfilePath": "ios/certs/profile.mobileprovision",
"distributionCertificate": {
"path": "ios/certs/dist-cert.p12",
"password": "iex3shi9Lohl"
}
}
}
```
> Remember to add **credentials.json** and all of your credentials to **.gitignore** so you don't accidentally commit them to the repository and potentially leak your secrets.
### Android credentials
If you want to build an Android app binary you'll need to have a keystore. If you don't have a release keystore yet, you can generate it on your own using the following command (replace `KEYSTORE_PASSWORD`, `KEY_PASSWORD`, `KEY_ALIAS` and `com.expo.your.android.package` with the values of your choice):
Once you have the keystore file on your computer, you should move it to the appropriate directory. We recommend you keep your keystores in the **android/keystores** directory. **Remember to git-ignore all your release keystores!** If you have run the above keytool command and placed the keystore at **android/keystores/release.keystore**, you can ignore that file by adding the following line to **.gitignore**:
```sh .gitignore
android/keystores/release.keystore
```
Create **credentials.json** and configure it with the credentials:
```json credentials.json
{
"android": {
"keystore": {
"keystorePath": "android/keystores/release.keystore",
"keystorePassword": "KEYSTORE_PASSWORD",
"keyAlias": "KEY_ALIAS",
"keyPassword": "KEY_PASSWORD"
}
},
"ios": {
}
}
```
- `keystorePath` points to where the keystore is located on your computer. Both relative (to the project root) and absolute paths are supported.
- `keystorePassword` is the keystore password. If you have followed the previous steps it's the value of `KEYSTORE_PASSWORD`.
- `keyAlias` is the key alias. If you have followed the previous steps it's the value of `KEY_ALIAS`.
- `keyPassword` is the key password. If you have followed the previous steps it's the value of `KEY_PASSWORD`.
### iOS credentials
There are a few more prerequisites for building the iOS app binary. You need a paid Apple Developer Account, and then you'll need to generate the Distribution Certificate and Provisioning Profile for your application, which can be done via the [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list).
Once you have the Distribution Certificate and Provisioning Profile on your computer, you should move them to the appropriate directory. We recommend you keep them in the `ios/certs` directory. In the rest of this document we assume that they are named **dist.p12** and **profile.mobileprovision** respectively.
> Remember to add directory with your credentials to **.gitignore**, so you don't accidentally commit them to the repository and potentially leak your secrets.
If you have placed the credentials in the suggested directory, you can ignore those files by adding the following line to **.gitignore**:
```sh .gitignore
ios/certs/*
```
Create (or edit) **credentials.json** and configure it with the credentials:
```json credentials.json
{
"android": {
},
"ios": {
"provisioningProfilePath": "ios/certs/profile.mobileprovision",
"distributionCertificate": {
"path": "ios/certs/dist.p12",
"password": "DISTRIBUTION_CERTIFICATE_PASSWORD"
}
}
}
```
- `provisioningProfilePath` points to where the Provisioning Profile is located on your computer. Both relative (to the project root) and absolute paths are supported.
- `distributionCertificate.path` points to where the Distribution Certificate is located on your computer. Both relative (to the project root) and absolute paths are supported.
- `distributionCertificate.password` is the password for the Distribution Certificate located at `distributionCertificate.path`.
#### Multi-target project
If your iOS app is using [App Extensions](https://developer.apple.com/app-extensions/) like Share Extension, Widget Extension, and so on, you need to provide credentials for every target of the Xcode project. This is necessary because each extension is identified by an individual bundle identifier.
Let's say that your project consists of a main application target (named `multitarget`) and a Share Extension target (named `shareextension`).
In this case, your **credentials.json** should look like below:
```json credentials.json
{
"ios": {
"multitarget": {
"provisioningProfilePath": "ios/certs/multitarget-profile.mobileprovision",
"distributionCertificate": {
"path": "ios/certs/dist.p12",
"password": "DISTRIBUTION_CERTIFICATE_PASSWORD"
}
},
"shareextension": {
"provisioningProfilePath": "ios/certs/shareextension-profile.mobileprovision",
"distributionCertificate": {
"path": "ios/certs/another-dist.p12",
"password": "ANOTHER_DISTRIBUTION_CERTIFICATE_PASSWORD"
}
}
}
}
```
## Setting a credentials source
You can tell EAS Build how it should resolve credentials by specifying `"credentialsSource": "local"` or `"credentialsSource:" "remote"` on a build profile.
- If `"local"` is provided, then **credentials.json** will be used.
- If `"remote"` is provided, then credentials will be resolved from EAS servers.
For example, maybe you want to use local credentials when deploying to the Amazon Appstore and remote credentials when deploying to the Google Play Store:
```json eas.json
{
"build": {
"amazon-production": {
"android": {
"credentialsSource": "local"
}
},
"google-production": {
"android": {
"credentialsSource": "remote"
}
}
}
}
```
If you do not set any option, `"credentialsSource"` will default to `"remote"`.
## Using local credentials on builds triggered from CI
Before you start setting up your CI job, make sure you have your **credentials.json** and **eas.json** files configured [as described above](#credentialsjson).
Developers tend to provide CI jobs with secrets by using environment variables. One of the challenges with this approach is that the **credentials.json** file contains a JSON object and it might be difficult to escape it properly, so you could assign it to an environment variable. One possible solution to this problem is to convert the file to a base64-encoded string, set an environment variable to that value, and later decode it and restore the file on the CI.
Consider the following steps:
- Run the following command in the console to generate Base64 string based on your credentials file:
```sh
$ base64 credentials.json
```
- On your CI, set the `CREDENTIALS_JSON_BASE64` environment variable with the output of the above command.
- In the CI job, restore the file using a simple shell command:
```sh
$ echo $CREDENTIALS_JSON_BASE64 | base64 -d > credentials.json
```
Similarly, you can encode your keystore, provisioning profile and distribution certificate so you can restore them later on the CI. To successfully trigger your build using local credentials from CI, you'll have to make sure all the credentials exist in the CI instance's file system (at the same locations as defined in **credentials.json**).
Once the restoring steps are in place, you can use the same process described in the [Triggering builds from CI](/build/building-on-ci) guide to trigger the builds.
## Using existing credentials
Learn about different options for supplying your app signing credentials to EAS Build.
EAS Build gives you two options for how you can supply your build jobs with app signing credentials:
1. [Automatically managed credentials](/app-signing/managed-credentials/): EAS can host your app signing credentials and take care of sharing them with teammates that have the necessary permissions.
2. [Local credentials](/app-signing/local-credentials/): You create a **credentials.json** file in your project that points to your keystore (Android) and/or provisioning profile and distribution certificate (iOS), along with associated passwords. This is uploaded from your local machine at the time any given build job is run, and disposed of once that build job has completed.
Regardless of which option you choose, your first step for using your existing set of credentials is to set them up as local credentials in **credentials.json**. Refer to the [credentials.json section of the local credentials guide](/app-signing/local-credentials/#credentialsjson) for more information on how to do this.
Once your **credentials.json** file is configured, you can run `eas credentials`, choose a platform, and then select `"Update credentials on Expo servers with values from credentials.json"` to upload them to be hosted and managed by EAS, if you would like. [Read more about syncing credentials](/app-signing/syncing-credentials/).
## Sync credentials between remote and local sources
Learn how to sync credentials between remote and local sources.
If you use automatically managed credentials, your credentials will be hosted remotely on EAS servers, but you may encounter a situation where you want to pull your credentials down to run a build locally. And if you use local credentials, you may find yourself in a position where you want to upload credentials specified in **credentials.json** up to EAS to be managed for you. Both of these are possible using the `eas credentials` command.
## Downloading credentials
To download your automatically managed credentials, run `eas credentials` in the root of your project, pick a platform, choose `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`, and then `"Download credentials from EAS to credentials.json"`. Run the command again to download the credentials for another platform, if needed.
Android credentials will be ready to use immediately because your project will read the credentials from **credentials.json**.
iOS credentials require two steps to set up locally. You will first need to install the distribution certificate into your keychain. Next, open your project Xcode and navigate to the "Signing & Capabilities" section, then import your provisioning profile and select it.
## Uploading credentials
To upload your credentials from **credentials.json** to be managed by EAS, run `eas credentials` in the root of your project, pick a platform, choose `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`, and then `"Upload credentials from credentials.json to EAS"`. Run the command again to upload the credentials for another platform, if needed.
## Security
Learn how credentials and other sensitive data are handled when using EAS.
Before you enter outside credentials or provide other sensitive data to third-party software you should ask yourself whether you trust the software to use it responsibly and protect it. Due to the nature of what goes into building an app binary for distribution on app stores, the Expo standalone app build service requires various pieces of information with varying degrees of sensitivity. This document explains what those are, how we store them, and what could go wrong if they were to be compromised.
Most data stored by Expo servers (credentials or otherwise) is encrypted at rest by our cloud provider, Google Cloud. Credentials are additionally encrypted using [KMS](https://cloud.google.com/security/products/security-key-management). Credentials are only unencrypted for as long as we need them in memory in the standalone app builders or push notification services. Credentials are always encrypted in our databases, message queues, and other less transient parts of the system.
All of the data related to the information explained below can be downloaded and removed from Expo servers (if it is stored there at all in the first place), and some of it may be available through other locations such as the Apple Developer Portal.
## Android Push Notification credentials
Android uses Firebase Cloud Messaging (FCM) for push notifications. If you build a standalone app with Expo we store your FCM server key for you.
### Consequences if compromised
Each FCM server key can send push notifications to any of the Android apps associated with the Firebase project to which the key belongs. A malicious actor would need to have access to the FCM server key and device tokens to send a notification.
You can create and delete server keys through the Firebase console. When you delete a key, notifications using that key will stop working. When you create a new one and upload it to Expo, notifications will resume working.
### Consequences if lost
None. You can access it through the Firebase console.
## Android build credentials
A keystore and keystore password are required to sign a build for release to the Play Store. These are encrypted with KMS and additionally at rest. After an app is first submitted to the Google Play Store, the same keystore must be used to sign the app again to update it. It proves that the APK came from the developer who owns the keystore. The keystore alone doesn't let you submit to Google Play — your Google account needs access to the Google Play Console as well.
### Consequences if compromised
Provided that your Google Play Developer account is secure, a malicious actor will not be able to update your app with your keystore and keystore password. You cannot change your keystore.
### Consequences if lost
You will not be able to update your app on Google Play. You may want to download and backup the keystore and keystore password in a secure location of your choosing or in Google Play with the App Signing feature.
## Google Developer credentials
Expo tools never ask you to provide your Google account credentials.
## Android submit credentials
### Google Service Account Key
Google Service Account Key is the authentication method used to submit an Android app to the Google Play Store with EAS Submit. This key is stored on Expo servers and encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) when at rest. The key will remain on Expo servers to be re-used for subsequent submissions, and it can be removed at any time by a user with the requisite permissions.
#### Consequences if compromised
If a malicious actor somehow gains access to your Google Service Account Key, they would be able to perform actions in the Google Play Console on your behalf. The actions they could perform would be limited to the permissions granted to the service account key.
If the attacker has additionally gained access to your upload keystore, they would be able to submit a new version of an existing app. The actor would not be able to submit a new app to the Google Play Store in your name, as the first Google Play submission needs to be done through the web console.
#### Consequences if lost
None. If you lose the Google Service Account Key, you can revoke it using the Google Cloud Console and create a new one.
## iOS Push Notification credentials
There are two types of iOS push notification credentials: one modern approach recommended by Apple and the legacy approach. The default behavior is to use the modern approach, but developers may opt-in to the legacy approach by providing a p12 certificate.
### APNs auth key (p8) + key ID (string)
Each developer account has up to two auth keys, each of which can send notifications to any app on the account.
Auth keys are revocable from the Apple Developer Center. If you revoke them, notifications will stop working. If you provision a new auth key and upload it to Expo then notifications will resume working. Device tokens are not invalidated when auth keys are revoked.
### Consequences if compromised
If a malicious actor were to somehow gain access to the credentials, they would be able to send push notifications to your app. However, they would need to know which device tokens to send them to.
### Consequences if lost
The Apple Developer console lets you download an APNs Auth Key only when it is created. If an Auth Key is lost, it can be revoked through the Apple Developer console and replaced with a new key.
## iOS build credentials
This refers to the production distribution certificate and password (which are automatically generated if you let Expo manage them for you) and provisioning profiles (which are not secret). Like most credential data stored by Expo these are all encrypted with KMS. Your build credentials let you build an app to upload to App Store Connect. To actually upload it and submit it for review, though, you need to have your Apple Developer account credentials.
### Consequences if compromised
There isn't much that a malicious actor could do with this alone — they would be unable to submit any apps without having your Apple Developer account credentials. You can revoke the distribution certificate and provisioning profile from the Apple Developer website.
### Consequences if lost
None. They are available through the Apple Developer console.
## Apple Developer account credentials
When creating a standalone app build, or uploading to the App Store you will be prompted for your Apple Developer account credentials. We do not store these on our servers — EAS CLI only uses them locally. Your computer alone provisions distribution certificates and auth keys that are sent to Expo servers; your developer credentials are not sent to Expo servers. An additional layer of security is enforced by Apple, as they require two-factor authentication for all Apple Developer accounts.
When creating ad-hoc builds, we temporarily store an Apple Developer session token used to create an ad-hoc provisioning profile with your development device's UDID. Once we are done using this session token we destroy it.
### Keychain
By default, your Apple ID credentials are stored in the macOS Keychain.
Your password is only ever stored locally on your computer. This feature is not available for Windows or Linux users.
Disable Keychain support with the environment variable `EXPO_NO_KEYCHAIN=1`. You can also use this to change the saved password.
### Changing Apple ID password in Keychain
To delete the locally stored password, open the "Keychain Access" app, switch to "All Items", and search for "deliver. [Your Apple ID]" (example: `deliver.bacon@expo.dev`). Select the item you wish to modify and delete it. Next time running an Expo command you'll be prompted for a new password.
### Consequences if compromised
For standalone builds, as explained above, your machine would need to be compromised for a malicious actor to have access to your username and password. They would also need to have access to your two-factor authentication code generator, which for Apple Developer accounts is a pre-authorized Apple device. At this point, you may have worse problems, but as you may expect, the actor would be able to do whatever they like with your Apple Developer account.
For ad-hoc builds, if a user were to gain access to your session token it would be comparable to being signed in to your account.
### Consequences if lost
None. They are available through the Apple Developer console.
## iOS submit credentials
### Apple App Store Connect (ASC) API key
Apple App Store Connect (ASC) API key is one of the authentication methods that can be used to submit an iOS app to Apple's App Store using the EAS Submit service. This key is stored on the Expo servers and encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) when at rest. The key will remain on Expo servers to be re-used for subsequent submissions, and it can be removed at any time by a user with the requisite permissions.
The ASC API key is the default and **recommended** authentication method for submitting your apps to the App Store using EAS Submit.
#### Consequences if compromised
If a malicious actor somehow gains access to the ASC API key, they would be able to perform actions in the App Store Connect on your behalf. The actions they could perform would be limited to the permissions granted to the API key.
If the attacker has additionally gained access to your build credentials, they would be able to submit a new version of an existing app. They would only be able to submit the app signed with those build credentials, and they can't submit any arbitrary app to the App Store in your name.
#### Consequences if lost
None. If you lose the ASC API key, you can revoke it using the App Store Connect portal and create a new one.
### Apple app-specific password
Apple app-specific password is another authentication method that can be used to submit an iOS app to Apple's App Store using the EAS Submit. Unlike other credentials, the app-specific password is not stored in the Expo servers between submissions, it must be provided each time it is to be used.
The password is encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) and stored only for the period required to submit the app to the App Store plus 24 hours, to allow for retries during that time. Once this period is over, the password is deleted from the Expo servers.
This authentication method is **not recommended**. We recommend using the Apple Store Connect (ASC) API key for submitting your apps to the App Store instead. Expo won't use the Apple app-specific password in any way other than to submit your app to the App Store.
#### Consequences if compromised
If a malicious actor somehow gains access to the app-specific password, they would be able to access information like mail, contacts, and calendars that you store in iCloud (check [Apple's documentation](https://support.apple.com/en-us/102654) for more details).
If the attacker has additionally gained access to your build credentials, they would be able to submit a new version of an existing app. They would only be able to submit the app signed with those build credentials, and they can't submit any arbitrary app to the App Store in your name.
#### Consequences if lost
None. If you lose the app-specific password, you can revoke it and create a new one in the Apple account settings.
## Device tokens for Android and iOS push notifications
On top of the platform-specific credentials, a device token is necessary to send a push notification. Expo manages this for you and provides an abstraction on top of it with the Expo Push Token. The device token identifies the recipient, that is, the device to whom the notification is being sent. The device tokens are encrypted at rest and periodically cycled automatically by Android and iOS.
### Consequences if compromised
If a malicious actor has access to the device tokens, they will be unable to do anything with them unless they also have the push notification credentials for the appropriate platform.
### Consequences if lost
You won't be able to send notifications to users until they open your app again.
## Need more control?
If the above information doesn't satisfy your security requirements, you may wish to run your standalone app builds [on your infrastructure](/build-reference/local-builds/). Note that you will still need to provide your push notification credentials to use the push notification service. If that is also impossible, we recommend handling push notifications on your own.
## Apple Developer Program roles and permissions for EAS Build
Learn about the Apple Developer account membership requirements for creating an EAS Build.
An Apple Developer account with permissions to create [app signing credentials](/app-signing/managed-credentials/#generating-app-signing-credentials), such as certificates, identifiers, and provisioning profiles, is required when using EAS Build to create iOS device builds. These credentials can be generated when submitting the build by logging into your Apple Account from the EAS CLI, or they can be uploaded to your Expo account by an authorized user, so users without Apple Developer account access can create builds using the uploaded credentials.
On individual Apple Developer accounts, only the Account Holder role can generate app signing credentials. On an organization Apple Developer account, the Account Holder and Admin roles can always generate app signing credentials, and the App Manager role can generate credentials when a user with this role has **Access to Certificates, Identifiers, and Profiles** enabled in their App Store Connect user permissions.
This guide provides steps that an authorized user can follow to ensure app signing credentials are generated and available to their team members who use EAS. It also provides steps for the team developer to create an EAS Build by using pre-generated credentials.
> See [Apple's documentation on Program Roles](https://developer.apple.com/support/roles/) for details on the different roles and their permissions based on the type of Developer account and the permissions that are required for each role.
## Steps for Apple Developer account's authorized user
The authorized user of the Apple Developer account needs to generate the following credentials:
- **Distribution signing certificate**: Required to sign development and release builds that are installed on an iOS device.
- **Ad hoc provisioning profile**: Required to sign builds that are installed on a device outside of the Apple App Store.
- **Distribution provisioning profile**: Required to sign the build that is submitted to the Apple App Store.
- **Push key**: Required when using a push notification service.
For details on Distribution certificate, Provision profiles, and Push keys, see [required iOS app credentials](/app-signing/app-credentials/#ios).
With EAS CLI, all of the above credentials can be created and synced automatically with the Apple Developer account. Once the authorized user logs in to their [Expo account](/accounts/account-types/), they can create or update the provisioning profile by running `eas credentials` using the EAS CLI.
```sh
$ eas login
$ eas credentials
```
The CLI will prompt for selecting a [build profile](/build/eas-json/#build-profiles) to use for the EAS Build. If the Apple Developer account's authorized user is creating a production build, follow these steps to [create a distribution provisioning profile](/tutorial/eas/ios-production-build/#create-a-distribution-provisioning-profile). To create a developer build, follow these steps to [create an ad hoc provisioning profile](/tutorial/eas/ios-development-build-for-devices/#provisioning-profile).
This ensures that the provisioning profile associated with the Expo account has necessary permissions.
> For projects with existing credentials, see [Using existing credentials](/app-signing/existing-credentials/) for details on how to sync these to EAS or manage them manually.
## Steps for the team developer
As a developer on the team, when running `eas build -p ios` in the terminal window, the EAS CLI asks you to login to an Apple Developer account.
```sh
? Do you want to log in to your Apple account? > (Y/n)
No problem! 👌 If any of the next steps will require Apple account access we will ask you again about it.
```
Press n to skip logging into Apple Developer account if you don't have access (and avoid logging into your personal Apple Developer account, if any). The CLI displays message about skipping provisioning profile validation and other app signing credential validation and will continue creating the EAS Build with existing credentials
The EAS CLI needs to use the provisioning profile associated with the Expo account to create a build for iOS. When you skip login, the EAS Build will use the last provisioning profile and other credentials that were updated by the Apple Developer account's authorized user in your organization's Expo account.
## Additional information
### Uploading pre-generated Apple credentials
Some development teams may choose to generate distribution certificates and provisioning profiles outside of EAS. These credentials can be added by any EAS user with Developer or higher permissions using `eas credentials` or under **Project** > **Configuration** > **Credentials** using the Expo dashboard.
When uploading the credentials, you will need the **.p12** and **.mobileprovision** files, and any passwords set when generating the distribution certificate.
### Provisioning profile expiry and updates
The associated provisioning profile needs to be updated if certain [iOS capabilities](/build-reference/ios-capabilities/) (such as, entitlements) are added or removed, or at the annual expiry of the profile. This step is handled by the Apple Developer account's authorized user.
### Federated Apple Developer accounts
#### EAS Build
EAS CLI can only accept an Apple account's email and password to login into your Apple Developer account. You cannot login into [Federated Apple Developer account](https://support.apple.com/en-in/guide/apple-business-manager/axmb19317543/web) and make updates to the distribution certificate or provisioning profile. If your build credentials do not require any changes, you can skip logging in. Then, you can proceed with the build and EAS CLI will continue using your current uploaded credentials.
However, you can provide an Apple Store Connect (ASC) API token with Admin access to check and update Apple credentials when running `eas build` command. Follow the steps in [Provide an ASC API Token for your Apple Team](/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team) to create a build by passing the required token value to the `eas build` command.
#### EAS Submit
EAS Submit uses the ASC API token for submitting to TestFlight. If you have a Federated Apple Developer account, you can follow the standard EAS Submit setup. It lets you automatically submit your builds using `eas build --auto-submit`.
# Custom builds
## Get started with custom builds
Learn how to extend EAS Build with custom builds.
Custom builds allow customizing the build process for your project by running commands before, during, or after the build process. Customized builds can run from EAS CLI or when running builds in a React Native CI/CD pipeline, like with [EAS Workflows](/eas/workflows/get-started/).
Step 1:
## Create a custom build config
To get started, create directories and a file named **.eas/build/hello-world.yml** at the same level as **eas.json**. The location and name of both directories are important for EAS Build to identify that a project contains a custom build config.
Inside the **hello-world.yml**, you'll write your custom build config. The filename is unimportant; you can name it whatever you want. The only requirement is that the file extension uses **.yml**.
Add the following custom build config steps in the file:
```yaml .eas/build/hello-world.yml
build:
name: Hello World!
steps:
- run: echo "Hello, world!"
```
Step 2:
## Add `config` property in eas.json
To use the custom build config, add the `config` property in **eas.json** under a build profile.
Let's create a new [build profile](/build/eas-json/#build-profiles) called `test` under `build` to run the custom config from the **test.yml** file:
```json eas.json
{
"build": {
"test": {
"config": "test.yml",
},
}
```
If you wish to use separate configs for each platform, you can create separate YAML config files for Android and iOS. For example:
```json eas.json
{
"build": {
"test": {
"ios": {
"config": "hello-ios.yml",
},
"android": {
"config": "hello-android.yml",
}
},
}
```
Step 3:
## Run a build to test the custom build config
To test the custom build config, run the following command:
```sh
$ eas build -p android -e test
```
After the build is complete, you can verify that the `echo "Hello World!"` script was executed by checking the logs on the build's detail page.
## Learn more
Check out the example repository for more detailed examples:
## Custom build configuration schema
A reference of configuration options for custom builds with EAS Build.
Creating custom builds for EAS Build helps customize the build process for your project.
## YAML syntax for custom builds
Custom build config files are stored inside the **.eas/build** directory path. They use YAML syntax and must have a `.yml` or `.yaml` file extension. If you are new to YAML or want to learn more about the syntax, see [Learn YAML in Y minutes](https://learnxinyminutes.com/docs/yaml/).
## `build`
Defined to describe a custom build configuration. All config options to create a custom build are specified under it.
### `name`
The name of your custom build that is used to identify it in the build logs. EAS Build uses this property to display the name of your build in the dashboard.
For example, the build name is `Run tests`:
```yaml
build:
# @info #
name: Run tests
# @end #
steps:
- eas/checkout
- run:
name: Install dependencies
command: npm install
```
### `steps`
Steps are used to describe a list of actions, either in the form of commands or function calls. These actions are executed when a custom build runs on EAS Build. You can define single or multiple steps in a build config. However, it is **required** to define at least one step per build.
Each step is configured with the following properties:
#### `steps[].run`
The `run` key is used to trigger a set of instructions. For example, a `run` key is used to install dependencies using the `npm install` command:
```yaml
build:
name: Install npm dependencies
steps:
- eas/checkout
# @info #
- run:
name: Install dependencies
command: npm install
# @end #
```
You can also use `steps[].run` to execute single or multiline shell commands:
```yaml
build:
name: Run inline shell commands
steps:
# @info #
- run: echo "Hello world"
- run: |
echo "Multiline"
echo "bash commands"
# @end #
```
#### Use a single step
For example, a build config with the following `steps` will print "Hello world":
```yaml
build:
name: Greeting
steps:
# @info #
- run: echo "Hello world"
# @end #
```
> **Note:** `-` before `run` counts as indentation.
#### Use multiple steps
When multiple `steps` are defined, they are executed sequentially. For example, a build config with the following `steps` will first check out the project, install npm dependencies, and then run a command to run tests:
```yaml
build:
name: Run tests
steps:
# @info #
- eas/checkout
- run:
name: Install dependencies
command: npm install
- run:
name: Run tests
command: |
echo "Running tests..."
npm test
# @end #
```
#### Sharing environment variables with other steps
Environment variables exported (using `export`) in one step's `command` are not automatically exposed to other steps. To share an environment variable with other steps, use the `set-env` executable.
`set-env` expects to be called with two arguments: environment variable's name and value. For example, `set-env NPM_TOKEN "abcdef"` will expose `$NPM_TOKEN` variable with value `abcdef` to other steps.
> **Note:** Variables shared with `set-env` are not automatically exported locally. You need to call `export` yourself.
```yaml
build:
name: Shared environment variable example
steps:
- run:
name: Set environment variables
command: |
set -x
# Set variable
ENV_TEST_LOCAL="present-only-in-current-shell-context"
# Set and export variable
export ENV_TEST_LOCAL_EXPORT="present-in-current-step"
# Set shared variable
set-env ENV_TEST_SET_ENV "present-in-following-steps"
# Will print "ENV_TEST_LOCAL: present-only-in-current-shell-context"
# because current shell has access to this local variable.
echo "ENV_TEST_LOCAL: $ENV_TEST_LOCAL"
# Will print "ENV_TEST_LOCAL_EXPORT: present-in-current-step"
# because export also sets the local variable value.
echo "ENV_TEST_LOCAL_EXPORT: $ENV_TEST_LOCAL_EXPORT"
# Will "ENV_TEST_SET_ENV: "
# because set-env does not set or export variables.
echo "ENV_TEST_SET_ENV: $ENV_TEST_SET_ENV"
# Will only print LOCALLY_EXPORTED_ENV,
# because it is the only export-ed variable.
env | grep ENV_TEST_
- run:
name: Check variables values in next step
command: |
set -x
# Will print "ENV_TEST_LOCAL: ", because ENV_TEST_LOCAL
# is only a local variable in previous step.
echo "ENV_TEST_LOCAL: $ENV_TEST_LOCAL"
# Will print "ENV_TEST_LOCAL_EXPORT: "
# because export does not share a variable to other steps.
echo "ENV_TEST_LOCAL_EXPORT: $ENV_TEST_LOCAL_EXPORT"
# Will print "ENV_TEST_SET_ENV: present-in-following-steps"
# because set-env "exported" variable to other steps.
echo "ENV_TEST_SET_ENV: $ENV_TEST_SET_ENV"
# Will only print ENV_TEST_SET_ENV,
# because set-env "exported" it to other steps.
env | grep ENV_TEST_
```
#### `steps[].run.name`
The name used in build logs to display the name of the step.
#### `steps[].run.command`
The `command` defines a custom shell command to run when a step is executed. It is **required** to define a command for each step. It can be a multiline shell command:
```yaml
build:
name: Run tests
steps:
- eas/checkout
- run:
name: Run tests
# @info #
command: |
echo "Running tests..."
npm test
# @end #
```
#### `steps[].run.working_directory`
The `working_directory` is used to define an existing directory from the project's root directory. After an existing path is defined in a step, using it changes the current directory for that step. For example, a step is created to list all the assets inside the **assets** directory, which is a directory in your Expo project. The `working_directory` is set to `assets`:
```yaml
build:
name: Demo
steps:
- eas/checkout
- run:
name: List assets
# @info #
working_directory: assets
# @end #
command: ls -la
```
#### `steps[].run.shell`
Used to define the default executable shell for a step. For example, the step's shell is set to `/bin/sh`:
```yaml
build:
name: Demo
steps:
- run:
# @info #
shell: /bin/sh
# @end #
command: |
echo "Steps can use another shell"
ps -p $$
```
#### `steps[].run.inputs`
Input values are provided to a step. For example, you can use `input` to provide a value:
```yaml
build:
name: Demo
steps:
- run:
name: Say Hi
# @info #
inputs:
name: Expo
# @end #
command: echo "Hi, ${ inputs.name }!"
```
#### `steps[].run.outputs`
An output value is expected during a step. For example, a step has an output value of `Hello world`:
```yaml
build:
name: Demo
steps:
- run:
name: Produce output
# @info #
outputs: [value]
# @end #
command: |
echo "Producing output for another step"
set-output value "Output from another step..."
```
#### `steps[].run.outputs.required`
An output value can use a boolean to indicate if the output value is required or not. For example, a function does not have a required output value:
```yaml
build:
name: Demo
steps:
- run:
name: Produce another output
id: id456
# @info #
outputs:
- required_param
- name: optional_param
required: false
# @end #
command: |
echo "Producing more output"
set-output required_param "abc 123 456"
```
#### `steps[].run.id`
Defining an `id` for a step allows:
- Calling the same function that produces one or more outputs multiple times
- Using the output from one step to another
#### Call the same function one or more times
For example, the following function generates a random number:
```yaml
functions:
random:
name: Generate random number
outputs: [value]
command: set-output value `random_number`
```
In a build config, let's use the `random` function to generate two random numbers and print them:
```yaml
build:
name: Functions Demo
steps:
- random:
id: random_1
- random:
id: random_2
- run:
name: Print random numbers
inputs:
random_1: ${ steps.random_1.value }
random_2: ${ steps.random_2.value }
command: |
echo "${ inputs.random_1 }"
echo "${ inputs.random_2 }"
```
#### Use output from one step to another
For example, the following build config demonstrates how to use output from one step to another:
```yaml
build:
name: Outputs demo
steps:
- run:
name: Produce output
id: id123 # <---- !!!
outputs: [foo]
command: |
echo "Producing output for another step"
set-output foo bar
- run:
name: Use output from another step
inputs:
foo: ${ steps.id123.foo }
command: |
echo "foo = \"${ inputs.foo }\""
```
## `functions`
Defined to describe a reusable function that can be used in a build config. All config options to create a function are specified with the following properties:
### `functions.[function_name]`
The `[function_name]` is the name of a function that you define to identify it in the `build.steps`. For example, you can define a function with the name `greetings`:
```yaml
functions:
# @info #
greetings:
# @end #
name: Say Hi!
```
### `functions.[function_name].name`
The name that is used in build logs to display the name of the function. For example, a function with the display name `Say Hi!`:
```yaml
functions:
greetings:
# @info #
name: Say Hi!
# @end #
```
### `functions.[function_name].inputs`
Input values are provided to a function.
#### `inputs[].name`
The name of the input value. It is used as an identifier to access the input value such as in bash command interpolation.
```yaml
functions:
greetings:
name: Say Hi!
inputs:
# @info #
- name: name
# @end #
default_value: Hello world
command: echo "${ inputs.name }!"
```
#### `inputs[].required`
Boolean to indicate if the input value is required or not. For example, a function does not have a required value:
```yaml
functions:
greetings:
name: Say Hi!
inputs:
- name: name
# @info #
required: false
# @end #
```
#### `inputs[].type`
The type of the input value. It can be either `string`, `num` or `json`.
Input values set in the function call as well as `default_value` and `allowed_values` for the function are validated against the type.
The default input `type` is `string`.
For example, a function has an input value of type `string`:
```yaml
functions:
greetings:
name: Say Hi!
inputs:
- name: name
# @info #
type: string
# @end #
- name: age
# @info #
type: num
# @end #
- name: other_data
# @info #
type: json
# @end #
```
#### `inputs[].default_value`
You can use `default_value` to provide one default input. For example, a function has a default value of `Hello world`:
```yaml
functions:
greetings:
name: Say Hi!
inputs:
- name: name
# @info #
default_value: Hello world
# @end #
```
#### `inputs[].allowed_values`
You can use `allowed_values` to provide multiple values in an array. For example, a function has multiple allowed values:
```yaml
functions:
greetings:
name: Say Hi!
inputs:
- name: name
default_value: Hello world
# @info #
allowed_values: [Hi, Hello, Hey]
# @end #
type: string
```
#### Multiple input values
Multiple input values can be provided to a function.
```yaml
functions:
greetings:
name: Say Hi!
# @info #
inputs:
- name: name
default_value: Expo
- name: greeting
default_value: Hi
allowed_values: [Hi, Hello]
# @end #
command: echo "${ inputs.greeting }, ${ inputs.name }!"
```
### `functions.[function_name].outputs`
An output value is expected from a function. For example, a function has an output value of `Hello world`:
```yaml
functions:
greetings:
name: Say Hi!
# @info #
outputs: [value]
# @end #
command: set-output value "Hello world"
```
#### `outputs[].name`
The name of the output value. It is used as an identifier to access the output value in another step:
```yaml
functions:
greetings:
name: Say Hi!
outputs:
# @info #
- name: name
# @end #
```
#### `outputs[].required`
Boolean to indicate if the output value is required or not. For example, a function does not have a required output value:
```yaml
functions:
greetings:
name: Say Hi!
outputs:
- name: value
# @info #
required: false
# @end #
```
### `functions.[function_name].command`
Used to define the command to run when a function is executed, if you wish the function to be a simple shell script. Each function is **required** to define either a `command` or a `path` to JS/TS module implementing the function. For example, the command `echo "Hello world"` is used to print a message:
```yaml
functions:
greetings:
name: Say Hi!
# @info #
command: echo "Hi!"
# @end #
```
### `functions.[function_name].path`
Used to define the path to a JavaScript/TypeScript module implementing the function. Each function is **required** to define either a `command` or a `path` property. For example, the path `./greetings` is used to execute a `greetings` function declared in the `greetings` module:
```yaml
functions:
greetings:
name: Say Hi!
# @info #
path: ./greetings
# @end #
```
> [Learn more about building and using custom TypeScript/JavaScript functions](/custom-builds/functions/).
### `functions.[function_name].shell`
Used to define the default executable shell for a step where a function is executed. For example, the step's shell is set to `/bin/sh`:
```yaml
functions:
greetings:
name: Say Hi!
# @info #
shell: /bin/sh
# @end #
command: echo "Hi!"
```
### `functions.[function_name].supported_platforms`
Used to define the supported platforms for a function. Defaults to all platforms. Allowed platforms: `darwin`, `linux`.
For example, the function's supported platform is `darwin` (macOS):
```yaml
functions:
greetings:
name: Say Hi!
# @info #
supported_platforms: [darwin]
# @end #
command: echo "Hi!"
```
## `import`
A config file path list used to import functions from other config files. Imported files cannot have the `build` section.
For example, the following build config imports two files and calls two imported functions - `say_hi` and `say_bye`.
```yaml build-and-test.yml
import:
- common-functions.yml
- another-file.yml
build:
steps:
- say_hi
- say_bye
```
```yaml common-functions.yml
functions:
say_hi:
name: Say Hi!
command: echo "Hi!"
```
```yaml another-file.yml
functions:
say_bye:
name: Say bye :(
command: echo "Bye!"
```
## Functions
### Built-in EAS functions
EAS provides a set of built-in reusable functions that you can use in a build config without defining the function definition.
> **info** **Tip:** Any function that is built-in and provided by EAS must start with the `eas/` prefix.
#### `eas/build`
The all-in-one function that encapsulates the entire EAS Build build process. It resolves the best build configuration based on your build profile's settings from [**eas.json**](/eas/json/).
It's ideal for people who want to have the build done without worrying about altering and configuring the build process manually. It can be a great starting point for your custom build configuration if you are interested in using other custom steps before or after the build process and you don't want to change the build process itself.
```yaml example.yml
build:
name: Run a build using a single command
steps:
- eas/build
```
To have more control over the build process and customize it as per your requirements, see the following custom functions and steps that run in the background by `eas/build`. They are executed as a build process based on your build profile's configuration.
##### Android
When a build configuration is using [`withoutCredentials`](/eas/json/#withoutcredentials):
- [`eas/checkout`](#eascheckout)
- [`eas/use_npm_token`](#easuse_npm_token)
- [`eas/install_node_modules`](#easinstall_node_modules)
- [`eas/resolve_build_config`](#easresolve_build_config)
- [`eas/prebuild`](#easprebuild)
- [`eas/configure_eas_update`](#easconfigure_eas_update)
- [`eas/run_gradle`](#easrun_gradle)
- [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts)
When a build configuration uses credentials (for both `internal` and `store` [distribution](/eas/json/#distribution) builds):
- [`eas/checkout`](#eascheckout)
- [`eas/use_npm_token`](#easuse_npm_token)
- [`eas/install_node_modules`](#easinstall_node_modules)
- [`eas/resolve_build_config`](#easresolve_build_config)
- [`eas/prebuild`](#easprebuild)
- [`eas/configure_eas_update`](#easconfigure_eas_update)
- [`eas/inject_android_credentials`](#easinject_android_credentials)
- [`eas/configure_android_version`](#easconfigure_android_version)
- [`eas/run_gradle`](#easrun_gradle)
- [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts)
##### iOS
When a build configuration is using [`withoutCredentials`](/eas/json/#withoutcredentials) or [`simulator`](/eas/json/#simulator):
- [`eas/checkout`](#eascheckout)
- [`eas/use_npm_token`](#easuse_npm_token)
- [`eas/install_node_modules`](#easinstall_node_modules)
- [`eas/resolve_build_config`](#easresolve_build_config)
- [`eas/prebuild`](#easprebuild)
- Install pods using the `pod install` command
- [`eas/configure_eas_update`](#easconfigure_eas_update)
- [`eas/generate_gymfile_from_template`](#easgenerate_gymfile_from_template)
- [`eas/run_fastlane`](#easrun_fastlane)
- [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts)
When a build configuration uses credentials (for both `internal` and `store` [distribution](/eas/json/#distribution) builds):
- [`eas/checkout`](#eascheckout)
- [`eas/use_npm_token`](#easuse_npm_token)
- [`eas/install_node_modules`](#easinstall_node_modules)
- [`eas/resolve_build_config`](#easresolve_build_config)
- [`eas/resolve_apple_team_id_from_credentials`](#easresolve_apple_team_id_from_credentials)
- [`eas/prebuild`](#easprebuild)
- Install pods using the `pod install` command
- [`eas/configure_eas_update`](#easconfigure_eas_update)
- [`eas/configure_ios_credentials`](#easconfigure_ios_credentials)
- [`eas/configure_ios_version`](#easconfigure_ios_version)
- [`eas/generate_gymfile_from_template`](#easgenerate_gymfile_from_template)
- [`eas/run_fastlane`](#easrun_fastlane)
- [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts)
You can replace the `eas/build` command call by using these steps in your YAML configuration file:
##### Known limitations
- It doesn't accept any inputs, and the resolved build process will be configured based on your build profile from [**eas.json**](/eas/json/).
- The build process produced by `eas/build` is not configurable and you can't customize it. If you need to customize the build process, use the subset of functions and steps that are executed behind the scenes by this function and configure them manually in the YAML configuration file, as shown in the examples above.
#### `eas/maestro_test`
All-in-one function that installs Maestro, prepares a testing environment (Android Emulator or iOS Simulator), and tests the app.
> **warning** Your project must be configured to use the old Build Infrastructure to start Android Emulator. Go to [Project settings](https://expo.dev/accounts/[account]/projects/[project]/settings) to configure. See [this changelog post](https://expo.dev/changelog/2024/08-29-c3d-default) for more information.
| Input | Type | Description |
| ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `flow_path` | `string` | Path (or multiple paths, each in a separate line) to [Maestro flows](https://maestro.mobile.dev/getting-started/writing-your-first-flow) to run. |
| `app_path` | `string` | Path (or regex pattern) to the emulator/simulator app that should be tested. If not provided, it defaults to **android/app/build/outputs/\*\*/\*.apk** for Android and to **ios/build/Build/Products/\*simulator/\*.app** for iOS. |
```yaml build-and-test.yml
build:
name: Build and test
steps:
- eas/build
# @info #
- eas/maestro_test:
inputs:
flow_path: |
maestro/sign_in.yml
maestro/create_post.yml
maestro/sign_out.yml
# @end #
```
```yaml test-ios-simulator-app.yml
build:
name: Build and test iOS simulator app
steps:
- eas/checkout
# @info #
- eas/maestro_test:
app_path: ./fixtures/my_app.app
inputs:
flow_path: |
maestro/sign_in.yml
maestro/create_post.yml
maestro/sign_out.yml
# @end #
```
```yaml test-android-emulator-app.yml
build:
name: Build and test Android emulator app
steps:
- eas/checkout
# @info #
- eas/maestro_test:
app_path: ./fixtures/my_app.apk
inputs:
flow_path: |
maestro/sign_in.yml
maestro/create_post.yml
maestro/sign_out.yml
# @end #
```
Behind the scenes, it uses:
- [`eas/install_maestro`](#easinstall_maestro) to install Maestro
- [`eas/start_android_emulator`](#easstart_android_emulator) to start an Android Emulator if needed
- [`eas/start_ios_simulator`](#easstart_ios_simulator) to start an iOS Simulator if needed
- Custom `run` to install **.apk** to the running Android Emulator and **.app** to iOS Simulator
- Series of `run` to execute `maestro test` for each of the provided flows
- [`eas/upload_artifact`](#easupload_artifact) to upload Maestro test artifacts as build artifact
> **Warning** We have observed that Maestro tests often time out if run on images with Xcode 15.0 or 15.2. Use the `latest` image to avoid any issues.
If you need to customize the Maestro version, run a specific Android Emulator or iOS Simulator, or upload multiple build artifacts you will need to write this series of steps yourself.
An example Android build config with eas/maestro_test expanded>}>
```yaml build-and-test-android-expanded.yml
build:
name: Build and test (Android, expanded)
steps:
- eas/build
# @info #
- eas/install_maestro
- eas/start_android_emulator:
inputs:
system_package_name: system-images;android-34;default;x86_64
- run:
command: |
# shopt -s globstar is necessary to add /**/ support
shopt -s globstar
# shopt -s nullglob is necessary not to try to install
# SEARCH_PATH literally if there are no matching files.
shopt -s nullglob
SEARCH_PATH="android/app/build/outputs/**/*.apk"
FILES_FOUND=false
for APP_PATH in $SEARCH_PATH; do
FILES_FOUND=true
echo "Installing \\"$APP_PATH\\""
adb install "$APP_PATH"
done
if ! $FILES_FOUND; then
echo "No files found matching \\"$SEARCH_PATH\\". Are you sure you've built an Emulator app?"
exit 1
fi
- run:
command: |
maestro test maestro/flow.yml
- eas/upload_artifact:
name: Upload test artifact
if: ${ always() }
inputs:
type: build-artifact
path: ${ eas.env.HOME }/.maestro/tests
# @end #
```
---
An example iOS build config with eas/maestro_test expanded>}>
```yaml build-and-test-ios-expanded.yml
build:
name: Build and test (iOS, expanded)
steps:
- eas/build
# @info #
- eas/install_maestro
- eas/start_ios_simulator
- run:
command: |
# shopt -s nullglob is necessary not to try to install
# SEARCH_PATH literally if there are no matching files.
shopt -s nullglob
SEARCH_PATH="ios/build/Build/Products/*simulator/*.app"
FILES_FOUND=false
for APP_PATH in $SEARCH_PATH; do
FILES_FOUND=true
echo "Installing \\"$APP_PATH\\""
xcrun simctl install booted "$APP_PATH"
done
if ! $FILES_FOUND; then
echo "No files found matching \\"$SEARCH_PATH\\". Are you sure you've built a Simulator app?"
exit 1
fi
- run:
command: |
maestro test maestro/flow.yml
- eas/upload_artifact:
name: Upload test artifact
if: ${ always() }
inputs:
type: build-artifact
path: ${ eas.env.HOME }/.maestro/tests
# @end #
```
---
#### `eas/checkout`
Checks out your project source files.
For example, a build config with the following `steps` will check out the project and list the files in the **assets** directory:
```yaml upload.yml
build:
name: List files
steps:
- eas/checkout
- run:
name: List assets
run: ls assets
```
#### `eas/use_npm_token`
Configures node package managers (npm, pnpm, or Yarn) for use with private packages, published either to npm or a private registry.
Set `NPM_TOKEN` in your project's secrets, and this function will configure the build environment by creating **.npmrc** with the token.
```yaml example.yml
build:
name: Install private npm modules
steps:
- eas/checkout
# @info #
- eas/use_npm_token
# @end #
- run:
name: Install dependencies
run: npm install # <---- Can now install private packages
```
#### `eas/install_node_modules`
Installs node modules using the package manager (npm, pnpm, or Yarn) detected based on your project. Works with monorepos.
```yaml example.yml
build:
name: Install node modules
steps:
- eas/checkout
# @info #
- eas/install_node_modules
# @end #
```
#### `eas/resolve_build_config`
Resolves and prints the build configuration. If the build has been triggered by the GitHub integration, it will update the current `job` and `metadata` context values. It should be called after installing the dependencies because the config may be influenced by config plugins.
This function is automatically executed by the [`eas/build`](#easbuild) function group.
#### `eas/get_credentials_for_build_triggered_by_github_integration`
> **warning** **Deprecated:** Replace this step with [`eas/resolve_build_config`](#easresolve_build_config).
#### `eas/resolve_apple_team_id_from_credentials`
> **Warning** This function is only available for iOS builds.
Resolves the Apple team ID value based on build credentials provided in the `inputs.credentials`. The resolved Apple team ID is stored in the `outputs.apple_team_id` output value.
```yaml example.yml
build:
name: Run prebuild script
steps:
- eas/checkout
- eas/install_node_modules
# @info #
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
# @end #
- eas/prebuild:
inputs:
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
```
| Property | Type | Description |
| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | The name of the step in the reusable function that shows in the build logs. Defaults to `Resolve Apple team ID from credentials`. |
| `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. |
#### `eas/prebuild`
Runs the `expo prebuild` command using the package manager (npm, pnpm, or Yarn) detected based on your project with the command best suited for your build type and build environment.
```yaml example.yml
build:
name: Run prebuild script
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
# @info #
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
# @end #
```
```yaml example.yml
build:
name: Run prebuild script
steps:
- eas/checkout
- eas/install_node_modules
# @info #
- eas/prebuild
# @end #
```
| Property | Type | Description |
| ---------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Prebuild`. |
| `inputs.clean` | `boolean` | Optional input defining whether the function should use `--clean` flag when running the command. Defaults to false |
| `inputs.apple_team_id` | `boolean` | Optional input defining Apple team ID which should be used when doing prebuild. It should be specified for iOS builds using credentials. |
#### `eas/configure_eas_update`
> **Warning** To use this function you need to have EAS Update configured for your project.
Configures runtime version and release channel for your build.
```yaml example.yml
build:
name: Configure EAS Update
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
# @info #
- eas/configure_eas_update
# @end #
```
```yaml example.yml
build:
name: Configure EAS Update
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
# @info #
- eas/configure_eas_update:
inputs:
runtime_version: 1.0.0
channel: mychannel
# @end #
```
| Property | Type | Description |
| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure EAS Update`. |
| `inputs.runtime_version` | `string` | Optional input defining runtime version which should be configured for the build. Defaults to `${ eas.job.version.runtimeVersion }` or natively defined runtime version. |
| `inputs.channel` | `string` | Optional input defining channel which should be configured for the build. Defaults to `${ eas.job.updates.channel }`. |
#### `eas/inject_android_credentials`
> **Warning** This function is only available for Android builds.
Configures Android keystore with credentials on the builder and injects app signing config using these credentials into gradle config.
```yaml example.yml
build:
name: Android credentials
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
# @info #
- eas/inject_android_credentials
# @end #
```
| Property | Type | Description |
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Inject Android credentials`. |
| `inputs.credentials` | `json` | Optional input defining the app credentials for your Android build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for Android. |
#### `eas/configure_ios_credentials`
> **Warning** This function is only available for iOS builds.
Configures iOS credentials on the builder. Modifies the configuration of the Xcode project by assigning provisioning profiles to the targets.
```yaml example.yml
build:
name: iOS credentials
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
# @info #
- eas/configure_ios_credentials
# @end #
```
| Property | Type | Description |
| ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure iOS credentials`. |
| `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. |
| `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. |
#### `eas/configure_android_version`
> **Warning** This function is only available for Android builds.
Configures the version of your Android app. It's used to set a version when using [remote app version management](/build-reference/app-versions/).
It's not mandatory to use this function, if it's not used the version from native code generated during the prebuild phase will be used.
```yaml example.yml
build:
name: Configure Android version
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/inject_android_credentials
# @info #
- eas/configure_android_version
# @end #
```
```yaml example.yml
build:
name: Configure Android version
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/inject_android_credentials
# @info #
- eas/configure_android_version:
inputs:
version_code: '123'
version_name: '1.0.0'
# @end #
```
| Property | Type | Description |
| --------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure Android version`. |
| `inputs.version_code` | `string` | Optional input defining `versionCode` of your Android build. Defaults to `${ eas.job.version.versionCode }` |
| `inputs.version_name` | `string` | Optional input defining `versionName` of your Android build. Defaults to `${ eas.job.version.versionName }`. |
#### `eas/configure_ios_version`
> **Warning** This function is only available for iOS builds.
Configures the version of your iOS app. It's used to set a version when using [remote app version management](/build-reference/app-versions/).
It's not mandatory to use this function, if it's not used the version from native code generated during the prebuild phase will be used.
```yaml example.yml
build:
name: Configure iOS version
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
# @info #
- eas/configure_ios_version
# @end #
```
```yaml example.yml
build:
name: Configure iOS version
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
# @info #
- eas/configure_ios_version:
inputs:
build_number: '123'
app_version: '1.0.0'
# @end #
```
| Property | Type | Description |
| ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure iOS version`. |
| `inputs.build_number` | `string` | Optional input defining the build number (`CFBundleVersion`) of your iOS build. Defaults to `${ eas.job.version.buildNumber }` |
| `inputs.app_version` | `string` | Optional input defining the app version (`CFBundleShortVersionString`) of your iOS build. Defaults to `${ eas.job.version.appVersion }`. |
| `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. |
| `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. |
#### `eas/run_gradle`
> **Warning** This function is only available for Android builds.
Runs a Gradle command to build an Android app.
```yaml example.yml
build:
name: Build Android app
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/inject_android_credentials
# @info #
- eas/run_gradle
# @end #
```
```yaml example.yml
build:
name: Build Android app
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/inject_android_credentials
# @info #
- eas/run_gradle:
inputs:
command: :app:bundleRelease
# @end #
```
| Property | Type | Description |
| ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Run gradle`. |
| `inputs.command` | `string` | Optional input defining the Gradle command to run to build the Android app. If not specified it is resolved based on the build configuration and contents of the `${ eas.job }` object. |
#### `eas/generate_gymfile_from_template`
> **Warning** This function is only available for iOS builds.
Generates a [`Gymfile`](https://docs.fastlane.tools/actions/gym/#gymfile) used to build the iOS app using Fastlane from a template.
Default template used when credentials are passed:
```ruby Gymfile
suppress_xcode_output(true)
clean(<%- CLEAN %>)
scheme("<%- SCHEME %>")
<% if (BUILD_CONFIGURATION) { %>
configuration("<%- BUILD_CONFIGURATION %>")
<% } %>
export_options({
method: "<%- EXPORT_METHOD %>",
provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %>
"<%- profile.BUNDLE_ID %>" => "<%- profile.UUID %>",<% }); %>
}<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>,
iCloudContainerEnvironment: "<%- ICLOUD_CONTAINER_ENVIRONMENT %>"
<% } %>
})
export_xcargs "OTHER_CODE_SIGN_FLAGS=\\"--keychain <%- KEYCHAIN_PATH %>\\""
disable_xcpretty(true)
buildlog_path("<%- LOGS_DIRECTORY %>")
output_directory("<%- OUTPUT_DIRECTORY %>")
```
Default template used when credentials are not passed (simulator build):
```ruby Gymfile
suppress_xcode_output(true)
clean(<%- CLEAN %>)
scheme("<%- SCHEME %>")
<% if (BUILD_CONFIGURATION) { %>
configuration("<%- BUILD_CONFIGURATION %>")
<% } %>
derived_data_path("<%- DERIVED_DATA_PATH %>")
skip_package_ipa(true)
skip_archive(true)
destination("<%- SCHEME_SIMULATOR_DESTINATION %>")
disable_xcpretty(true)
buildlog_path("<%- LOGS_DIRECTORY %>")
```
`CLEAN`, `SCHEME`, `BUILD_CONFIGURATION`, `EXPORT_METHOD`, `PROFILES`, `ICLOUD_CONTAINER_ENVIRONMENT`, `KEYCHAIN_PATH`, `LOGS_DIRECTORY`, `OUTPUT_DIRECTORY`, `DERIVED_DATA_PATH`and `SCHEME_SIMULATOR_DESTINATION` values are provided to the template based on the inputs and default internal configuration of EAS Build.
```yaml example.yml
build:
name: Generate Gymfile template
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
# @info #
- eas/generate_gymfile_from_template:
inputs:
credentials: ${ eas.job.secrets.buildCredentials }
# @end #
```
```yaml example.yml
build:
name: Generate Gymfile template
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
# @info #
- eas/generate_gymfile_from_template
# @end #
```
However, you can also use other custom properties in the template, by specifying your custom template in `inputs.template` and providing the values for the custom properties in the `inputs.extra` object.
```yaml example.yml
build:
name: Generate Gymfile template
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
# @info #
- eas/generate_gymfile_from_template:
inputs:
credentials: ${ eas.job.secrets.buildCredentials }
extra:
MY_VALUE: my value
template: |
suppress_xcode_output(true)
clean(<%- CLEAN %>)
scheme("<%- SCHEME %>")
<% if (BUILD_CONFIGURATION) { %>
configuration("<%- BUILD_CONFIGURATION %>")
<% } %>
export_options({
method: "<%- EXPORT_METHOD %>",
provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %>
"<%- profile.BUNDLE_ID %>" => "<%- profile.UUID %>",<% }); %>
}<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>,
iCloudContainerEnvironment: "<%- ICLOUD_CONTAINER_ENVIRONMENT %>"
<% } %>
})
export_xcargs "OTHER_CODE_SIGN_FLAGS=\"--keychain <%- KEYCHAIN_PATH %>\""
disable_xcpretty(true)
buildlog_path("<%- LOGS_DIRECTORY %>")
output_directory("<%- OUTPUT_DIRECTORY %>")
sth_else("<%- MY_VALUE %>")
# @end #
```
| Property | Type | Description |
| ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Generate Gymfile from template`. |
| `inputs.template` | `string` | Optional input defining the Gymfile template which should be used. If not specified one out of two default templates will be used depending on whether the `inputs.credentials` value is specified. |
| `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. If specified `KEYCHAIN_PATH`, `EXPORT_METHOD`, and `PROFILES` values will be provided to the template. |
| `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. Corresponds to the `BUILD_CONFIGURATION` template value. |
| `inputs.scheme` | `string` | Optional input defining the Xcode project's scheme which should be used for the build. Defaults to `${ eas.job.scheme }` or if not specified is resolved to the first scheme found in the Xcode project. Corresponds to the `SCHEME` template value. |
| `inputs.clean` | `boolean` | Optional input defining whether the Xcode project should be cleaned before the build. Defaults to `true`. Corresponds to `CLEAN` template variable. |
| `inputs.extra` | `json` | Optional input defining extra values which should be provided to the template. |
#### `eas/run_fastlane`
> **Warning** This function is only available for iOS builds.
Runs [`fastlane gym`](https://docs.fastlane.tools/actions/gym/#gym) command against the [`Gymfile`](https://docs.fastlane.tools/actions/gym/#gymfile) located in the `ios` project directory to build the iOS app.
```yaml example.yml
build:
name: Build iOS app
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
- eas/generate_gymfile_from_template:
inputs:
credentials: ${ eas.job.secrets.buildCredentials }
# @info #
- eas/run_fastlane
# @end #
```
```yaml example.yml
build:
name: Build iOS app
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/generate_gymfile_from_template
# @info #
- eas/run_fastlane
# @end #
```
#### `eas/find_and_upload_build_artifacts`
> **Warning** **You can currently upload each artifact type only once per build job.**If you use [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) while having [`buildArtifactPaths`](/eas/json/#buildartifactpaths) configured in your build profile and the step finds and uploads some build artifacts, any following `eas/upload_artifact` step will fail.To solve this, for now, we recommend removing `buildArtifactPaths` from custom build's profiles and uploading artifacts manually with `eas/upload_artifact` in the YAML if you need to call it there.
Automatically finds and uploads application archive, additional build artifacts, and Xcode logs from the default locations and using the [`buildArtifactPaths`](/eas/json#buildartifactpaths) configuration. Uploads found artifacts to the EAS servers.
```yaml example.yml|collapseHeight=440
build:
name: Build iOS app
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
clean: false
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- eas/configure_eas_update
- eas/configure_ios_credentials
- eas/generate_gymfile_from_template:
inputs:
credentials: ${ eas.job.secrets.buildCredentials }
- eas/run_fastlane
# @info #
- eas/find_and_upload_build_artifacts
# @end #
```
```yaml example.yml
build:
name: Build iOS app
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/generate_gymfile_from_template
- eas/run_fastlane
# @info #
- eas/find_and_upload_build_artifacts
# @end #
```
```yaml example.yml
build:
name: Build Android app
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/configure_eas_update
- eas/inject_android_credentials
- eas/run_gradle
- eas/find_and_upload_build_artifacts
```
#### `eas/upload_artifact`
Uploads build artifacts from provided paths.
> **Warning** **You can currently upload each artifact type only once per build job.**If you use [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) while having [`buildArtifactPaths`](/eas/json/#buildartifactpaths) configured in your build profile and the step finds and uploads some build artifacts, any following `eas/upload_artifact` step will fail.To solve this, for now, we recommend removing `buildArtifactPaths` from custom build's profiles and uploading artifacts manually with `eas/upload_artifact` in the YAML if you need to call it there.
For example, a build config with the following `steps` will upload an artifact to the EAS servers:
```yaml upload.yml
build:
name: Upload artifacts
steps:
- eas/checkout
# - ...
- eas/upload_artifact:
name: Upload application archive
inputs:
path: fixtures/app-debug.apk
- eas/upload_artifact:
name: Upload artifacts
inputs:
type: build-artifact
path: |
assets/*.jpg
assets/*.png
```
| Input | Type | Description |
| ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `path` | `string` | Required. Path or newline-delimited list of paths to the artifacts to upload to EAS servers. You can use `*` wildcard and other [glob patterns that `fast-glob` supports](https://github.com/mrmlnc/fast-glob#pattern-syntax). |
| `type` | `string` | The type of artifact that is uploaded to the EAS servers. Allowed values are `application-archive` and `build-artifact`. Defaults to `application-archive`. |
#### `eas/install_maestro`
Makes sure [Maestro](https://maestro.mobile.dev), the mobile UI testing framework, is installed along with all its dependencies.
```yaml build-and-test.yml
build:
name: Build and test
steps:
- eas/build
# ... simulator/emulator setup
# @info #
- eas/install_maestro:
# @end #
inputs:
# @info If you need to, you can provide the Maestro version to install. #
maestro_version: 1.35.0
# @end #
- run:
command: maestro test flows/signin.yml
- eas/upload_artifact:
name: Upload Maestro artifacts
inputs:
type: build-artifact
path: ${ eas.env.HOME }/.maestro/tests
```
| Input | Type | Description |
| ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `maestro_version` | `string` | Maestro version to install (for example, 1.35.0). If not provided, `install_maestro` will install the latest version. |
#### `eas/start_android_emulator`
Starts an Android Emulator you can use to test your apps on. Only available when running a build for Android.
> **warning** Your project must be configured to use the old Build Infrastructure to start Android Emulator. Go to [Project settings](https://expo.dev/accounts/[account]/projects/[project]/settings) to configure. See [this changelog post](https://expo.dev/changelog/2024/08-29-c3d-default) for more information.
```yaml build-and-test.yml
build:
name: Build and test
steps:
- eas/build
# @info #
- eas/start_android_emulator:
inputs:
system_image_package: system-images;android-30;default;x86_64
# @end #
# ... Maestro setup and tests
```
| Input | Type | Description |
| ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_name` | `string` | Name for the created device. You can customize it if starting multiple emulators. |
| `system_image_package` | `string` | Android package path to use for the emulator. For example, `system-images;android-30;default;x86_64`.To get a list of available system images, run [`sdkmanager --list`](https://developer.android.com/tools/sdkmanager#list) on a local computer. VMs run on x86_64 architecture, so always choose `x86_64` package variants. The [`sdkmanager` tool](https://developer.android.com/tools/sdkmanager) comes from Android SDK command-line tools. |
#### `eas/start_ios_simulator`
Starts an iOS Simulator you can use to test your apps on. Only available when running a build for iOS.
```yaml build-and-test.yml
build:
name: Build and test
steps:
- eas/build
# @info #
- eas/start_ios_simulator
# @end #
# ... Maestro setup and tests
```
| Input | Type | Description |
| ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `device_identifier` | `string` | Name or UDID of the Simulator you want to start. Examples include `iPhone [XY] Pro`, `AEF997BB-222C-4379-89BA-D21070B1D787`.**Note:** Available Simulators are different for every image. If you change the image, the Simulator for a given name may become unavailable. For instance, an Xcode 14 image will have iPhone 14 Simulators, while an Xcode 15 image will have iPhone 15 simulators. In general, we encourage not providing this input. See [runner images](/build/eas-json/#selecting-a-base-image) for more information. |
#### `eas/send_slack_message`
Sends a specified message to a configured [Slack webhook URL](https://api.slack.com/messaging/webhooks), which then posts it in the related Slack channel.
The message can be specified as plain text or as a [Slack Block Kit](https://api.slack.com/block-kit) message.
With both cases, you can reference build job properties and [use other steps outputs](#use-output-from-one-step-to-another) in the message for dynamic evaluation.
For example, `'Build URL: ${ eas.job.expoBuildUrl }'`, `Build finished with status: ${ steps.run_fastlane.status_text }`, `Build failed with error: ${ steps.run_gradle.error_text }`.
Either "message" or "payload" has to be specified, but not both.
```yaml send-slack-message.yml
build:
name: Slack your team from custom build
steps:
# @info #
- eas/send_slack_message:
name: Send Slack message to a given webhook URL
inputs:
message: 'This is a message to plain input URL'
slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'
# @end #
- eas/send_slack_message:
name: Send Slack message to a default webhook URL from SLACK_HOOK_URL secret
inputs:
message: 'This is a test message to default URL from SLACK_HOOK_URL secret'
- eas/send_slack_message:
name: Send Slack message to a webhook URL from specified secret
inputs:
message: 'This is a test message to a URL from specified secret'
slack_hook_url: ${ eas.env.ANOTHER_SLACK_HOOK_URL }
- eas/build
- eas/send_slack_message:
if: ${ always() }
name: Send Slack message when the build finishes (Android)
inputs:
message: |
This is a test message when Android build finishes
Status: `${ steps.run_gradle.status_text }`
Link: `${ eas.job.expoBuildUrl }`
- eas/send_slack_message:
if: ${ always() }
name: Send Slack message when the build finishes (iOS)
inputs:
message: |
This is a test message when iOS build finishes
Status: `${ steps.run_fastlane.status_text }`
Link: `${ eas.job.expoBuildUrl }`
- eas/send_slack_message:
if: ${ failure() }
name: Send Slack message when the build fails (Android)
inputs:
message: |
This is a test message when Android build fails
Error: `${ steps.run_gradle.error_text }`
- eas/send_slack_message:
if: ${ failure() }
name: Send Slack message when the build fails (iOS)
inputs:
message: |
This is a test message when iOS build fails
Error: `${ steps.run_fastlane.error_text }`
- eas/send_slack_message:
if: ${ success() }
name: Send Slack message when the build succeeds
inputs:
message: |
This is a test message when build succeeds
- eas/send_slack_message:
if: ${ always() }
name: Send Slack message with Slack Block Kit layout
inputs:
payload:
blocks:
- type: section
text:
type: mrkdwn
text: |-
Hello, Sir Developer
*Your build has finished!*
- type: divider
- type: section
text:
type: mrkdwn
text: |-
*${ eas.env.EAS_BUILD_ID }*
*Status:* `${ steps.run_gradle.status_text }`
*Link:* `${ eas.job.expoBuildUrl }`
accessory:
type: image
image_url: [your_image_url]
alt_text: alt text for image
- type: divider
- type: actions
elements:
- type: button
text:
type: plain_text
text: 'Do a thing :rocket:'
emoji: true
value: a_thing
- type: button
text:
type: plain_text
text: 'Do another thing :x:'
emoji: true
value: another_thing
```
| Input | Type | Description |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message` | `string` | The text of the message you want to send. For example, `'This is the content of the message'`. **Note:** Either `message` or `payload` needs to be provided, but not both. |
| `payload` | `string` | The contents of the message you want to send defined using [Slack Block Kit](https://api.slack.com/block-kit) layout. **Note:** Either `message` or `payload` needs to be provided, but not both. |
| `slack_hook_url` | `string` | The URL of the previously configured Slack webhook URL, which will post your message to the specified channel. You can provide the plain URL like `slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'`, use EAS secrets like `slack_hook_url: ${ eas.env.ANOTHER_SLACK_HOOK_URL }`, or set the `SLACK_HOOK_URL` secret, which will serve as a default webhook URL (in this last case, there is no need to provide `slack_hook_url` input). |
### Using built-in EAS functions to build an app
Using the built-in EAS functions you can recreate the default EAS Build process for different build types.
For example, to trigger a build that creates internal distribution build for Android and a simulator build for iOS you can use the following configuration:
```json eas.json|collapseHeight=420
{
"build": {
"developmentBuild": {
"distribution": "internal",
"android": {
"config": "development-build-android.yml"
},
"ios": {
"simulator": true,
"config": "development-build-ios.yml"
}
}
}
}
```
```yaml .eas/build/development-build-android.yml
build:
name: Simple internal distribution Android build
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/inject_android_credentials
- eas/run_gradle
- eas/find_and_upload_build_artifacts
```
```yaml .eas/build/development-build-ios.yml|collapseHeight=440
build:
name: Simple simulator iOS build
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- run:
name: Install pods
working_directory: ./ios
command: pod install
- eas/generate_gymfile_from_template
- eas/run_fastlane
- eas/find_and_upload_build_artifacts
```
To create a Google Play Store build for Android and an Apple App Store build for iOS you can use the following configuration:
```json eas.json
{
"build": {
"productionBuild": {
"android": {
"config": "production-build-android.yml"
},
"ios": {
"config": "production-build-ios.yml"
}
}
}
}
```
```yaml .eas/build/production-build-android.yml
build:
name: Customized Android Play Store build example
steps:
- eas/checkout
- eas/install_node_modules
- eas/prebuild
- eas/inject_android_credentials
- eas/run_gradle
- eas/find_and_upload_build_artifacts
```
```yaml .eas/build/production-build-ios.yml
build:
name: Customized iOS App Store build example
steps:
- eas/checkout
- eas/install_node_modules
- eas/resolve_apple_team_id_from_credentials:
id: resolve_apple_team_id_from_credentials
- eas/prebuild:
inputs:
apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id }
- run:
name: Install pods
working_directory: ./ios
command: pod install
- eas/configure_ios_credentials
- eas/generate_gymfile_from_template:
inputs:
credentials: ${ eas.job.secrets.buildCredentials }
- eas/run_fastlane
- eas/find_and_upload_build_artifacts
```
Check out the **example repository** for more detailed examples:
### Use a reusable function in a `build`
For example, a custom build config with the following reusable function contains a single command to print a message that is echoed.
```yaml
functions:
greetings:
- name: name
default_value: Hello world
inputs: [value]
command: echo "${ inputs.name }, { inputs.value }"
```
The above function can be used in a `build` as follows:
```yaml
build:
name: Functions Demo
steps:
# @info #
- greetings:
inputs:
value: Expo
# @end #
```
> **info** **Tip:** `build.steps` can execute multiple reusable `functions` sequentially.
## Override values in a `build`
You can override values for the following properties:
- `working_directory`
- `name`
- `shell`
For example, a reusable function called `list_files`:
```yaml
functions:
# @info #
list_files:
name: List files
command: ls -la
# @end #
```
When `list_files` is called in a build config, it lists all files in the root directory of a project:
```yaml
build:
name: List files
# @info #
steps:
- eas/checkout
- list_files
# @end #
```
You can use the `working_directory` property to override the behavior in the function call to list the files in a different directory by specifying the path to that directory:
```yaml
build:
name: List files
steps:
# @info #
- eas/checkout
- list_files:
working_directory: /a/b/c
# @end #
```
## TypeScript functions
Learn how to create and use EAS Build functions in your custom build configurations.
EAS Build functions are a great way to extend the functionality of custom builds. You can use them to create reusable steps, and to write your logic in JavaScript, TypeScript, or Bash (more information in [`command` in the config schema](./schema.mdx#functionsfunction_namecommand)). This guide provides a walkthrough of creating a function in TypeScript.
Step 1:
## Initialize an EAS Build function module
The easiest way to create an EAS Build function is to use the `create-eas-build-function` CLI tool. By running the following command from the same directory as your **eas.json** file, you can create a new custom TypeScript function:
```sh
$ npx create-eas-build-function@latest ./.eas/build/myFunction
```
This creates a new module called `myFunction` in the **.eas/build** directory. The module will contain a pre-generated module configuration and the **src** directory with the **index.ts** file containing the default TypeScript function template.
```ts .eas/build/myFunction/src/index.ts
// This file was autogenerated by `create-eas-build-function` command.
// Go to README.md to learn more about how to write your own custom build functions.
import { BuildStepContext } from '@expo/steps';
// interface FunctionInputs {
// // specify the type of the inputs value and whether they are required here
// // example: name: BuildStepInput;
// }
// interface FunctionOutputs {
// // specify the function outputs and whether they are required here
// // example: name: BuildStepOutput;
// }
async function myFunction(
ctx: BuildStepContext
// {
// inputs,
// outputs,
// env,
// }: {
// inputs: FunctionInputs;
// outputs: FunctionOutputs;
// env: BuildStepEnv;
// }
): Promise {
ctx.logger.info('Hello from my TypeScript function!');
}
export default myFunction;
```
Step 2:
## Compile the function
Functions must be compiled to a single JavaScript file that can be run without installing any dependencies. The default `build` script for generated functions uses [ncc](https://github.com/vercel/ncc) to compile your function into a single file with all its dependencies. If you don't have the `ncc` installed globally on your machine, run `npm install -g @vercel/ncc` to install it. Next, run the build script in the **.eas/build/myFunction** directory:
```sh
$ npm run build
```
This command triggers the `build` script placed in the **package.json** file of your custom function module.
```json package.json
{
...
"scripts": {
...
...
},
...
}
```
The `build` script generates **build/index.js**. This file must be uploaded to EAS Build as a part of your project archive, so that the builder can run your function. Ensure that the file is not excluded by a **.gitignore** file or **.easignore** file.
Step 3:
## Expose the function to the custom build config
> **Note**: The following example assumes that you have already set up a custom build workflow and configured it in your **eas.json**. If not, see [Get started with custom builds](/custom-builds/get-started/#create-a-workflow) before proceeding.
Let's assume that you have a **config.yml** file in the **.eas/build** directory. It contains the following content:
```yaml .eas/build/config.yml
build:
name: My example config
steps:
- eas/checkout
- eas/install_node_modules
- run:
name: Finished
command: echo Finished
```
To add your function to the config, you need to add the following lines to the **config.yml** file:
```yaml .eas/build/config.yml
build:
name: My example config
steps:
- eas/checkout
- eas/install_node_modules
- run:
name: Finished
command: echo Finished
# @info #
functions:
my_function:
name: My function
path: ./myFunction
# @end #
```
The `path` property should be a relative path from the config file to your function directory. In this case, it's just `./myFunction`.
Now, add a call to the `my_function` function in the **config.yml** file:
```yaml .eas/build/config.yml
build:
name: My example config
steps:
- eas/checkout
- eas/install_node_modules
# @info #
- my_function
# @end #
- run:
name: Finished
command: echo Finished
functions:
my_function:
name: My function
path: ./myFunction
```
Step 4:
## Working on the function
For a more advanced example, let's say you want to make a function calculate the sum of two numbers and print the result to the console, and then output that value from the function. To do this, modify the **config.yml** and **index.ts** files to make the function accept two inputs called `num1` and `num2` and return their sum as an output called `sum`.
```yaml .eas/build/config.yml
build:
name: My example config
steps:
- eas/checkout
- eas/install_node_modules
# @info #
- my_function:
inputs:
num1: 1
num2: 2
id: sum_function
# @end #
- run:
name: Print the sum
inputs:
sum: ${ steps.sum_function.sum }
command: echo ${ inputs.sum }
- run:
name: Finished
command: echo Finished
functions:
my_function:
name: My function
# @info #
inputs:
- name: num1
type: number
- name: num2
type: number
outputs:
- name: sum
# @end #
path: ./myFunction
```
```ts .eas/build/myFunction/src/index.ts
// This file was autogenerated by `create-eas-build-function` command.
// Go to README.md to learn more about how to write your own custom build functions.
import {
BuildStepContext,
BuildStepInput,
BuildStepInputValueTypeName,
BuildStepOutput,
} from '@expo/steps';
interface FunctionInputs {
// first template argument is the type of the input value, second template argument is a boolean indicating if the input is required
num1: BuildStepInput;
num2: BuildStepInput;
}
interface FunctionOutputs {
// template argument is a boolean indicating if the output is required
sum: BuildStepOutput;
}
async function myFunction(
ctx: BuildStepContext,
{
inputs,
outputs,
}: // env,
{
inputs: FunctionInputs;
outputs: FunctionOutputs;
// env: BuildStepEnv;
}
): Promise {
ctx.logger.info(`num1: ${inputs.num1.value}`);
ctx.logger.info(`num2: ${inputs.num2.value}`);
const sum = inputs.num1.value + inputs.num2.value;
ctx.logger.info(`sum: ${sum}`);
outputs.sum.set(sum.toString()); // Currently, outputs must be strings. This will improve in the future.
}
export default myFunction;
```
> **Info** Remember to compile your function each time you make changes to it: `npm run build`.
## Summary
- Writing functions is a great way to extend the functionality of custom builds with your own logic.
- EAS Build functions are reusable — you can use them in multiple custom build configurations.
- Using EAS Build functions is a great option for more advanced use cases that are not easy to do by writing shell scripts.
- Most of the [built-in functions](/custom-builds/schema/#built-in-eas-functions) are open-source and can be forked or used as a reference for writing your own functions.
Check out the **example repository** for more detailed examples:
# Reference
## Build lifecycle hooks
Learn how to use the EAS Build lifecycle hooks with npm to customize your build process.
EAS Build lifecycle npm hooks allows you to customize your build process by running scripts before or after the build process.
> For better understanding, see the [Android build process](/build-reference/android-builds/) and the [iOS build process](/build-reference/ios-builds/).
> **warning** The lifecycle hooks are not executed by the build process in [custom builds](/custom-builds/get-started/). They need to be manually extracted and called by the build steps during the process.
## EAS Build lifecycle hooks
There are six EAS Build lifecycle npm hooks available. To use, them, you can set them in your **package.json**.
| Build Lifecycle npm hook | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eas-build-pre-install` | Executed before EAS Build runs `npm install`. |
| `eas-build-post-install` | The behavior depends on the platform and project type. For Android, runs once after the following commands have all completed: `npm install` and `npx expo prebuild` (if needed). For iOS, runs once after the following commands have all completed: `npm install`, `npx expo prebuild` (if needed), and `pod install`. |
| `eas-build-on-success` | This hook is triggered at the end of the build process if the build was successful. |
| `eas-build-on-error` | This hook is triggered at the end of the build process if the build failed. |
| `eas-build-on-complete` | This hook is triggered at the end of the build process. You can check the build's status with the `EAS_BUILD_STATUS` environment variable. It's either `finished` or `errored`. |
| `eas-build-on-cancel` | This hook is triggered if the build is canceled. |
An example of how a **package.json** can look when using one or more lifecycle hooks:
```json package.json
{
"name": "my-app",
"scripts": {
"eas-build-pre-install": "echo 123",
"eas-build-post-install": "echo 456",
"eas-build-on-success": "echo 789",
"eas-build-on-error": "echo 012",
"eas-build-on-cancel": "echo 345",
"start": "expo start",
"test": "jest"
},
"dependencies": {
"expo": "51.0.0"
}
}
```
## Platform-specific hook behavior
To run a script (or some part of a script) only for Android or iOS builds, you can fork the behavior depending on the platform within the script. See the following common examples to do this through a shell script or a Node script.
### Examples
#### package.json and shell script
```json package.json
{
"name": "my-app",
"scripts": {
"eas-build-pre-install": "./pre-install",
"start": "expo start"
},
"dependencies": {
}
}
```
```bash pre-install
#!/bin/bash
# This is a file called "pre-install" in the root of the project
if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then
echo "Run commands for Android builds here"
elif [[ "$EAS_BUILD_PLATFORM" == "ios" ]]; then
echo "Run commands for iOS builds here"
fi
```
Note: Example: Pre-install script that installs on macOS workers
---
The following script installs [`git-lfs`](https://git-lfs.com/) if it is not yet installed. This is useful in some cases where `git-lfs` is required to install certain CocoaPods.
```bash pre-install
if [[ "$EAS_BUILD_PLATFORM" == "ios" ]]; then
if brew list git-lfs > /dev/null 2>&1; then
echo "=====> git-lfs is already installed."
else
echo "=====> Installing git-lfs"
HOMEBREW_NO_AUTO_UPDATE=1 brew install git-lfs
git lfs install
fi
fi
```
---
#### package.json and Node script
```json package.json
{
"name": "my-app",
"scripts": {
"eas-build-pre-install": "node pre-install.js",
"start": "expo start"
// ...
},
"dependencies": {
// ...
}
}
```
```js pre-install.js
// Create a file called "pre-install.js" at the root of the project
if (process.env.EAS_BUILD_PLATFORM === 'android') {
console.log('Run commands for Android builds here');
} else if (process.env.EAS_BUILD_PLATFORM === 'ios') {
console.log('Run commands for iOS builds here');
}
```
## Using private npm packages
Learn how to configure EAS Build to use private npm packages.
EAS Build has full support for using private npm packages in your project. These can either be published to npm (if you have [the Pro/Teams plan](https://www.npmjs.com/products)) or to a private registry (for example, using self-hosted [Verdaccio](https://verdaccio.org/)).
Before starting the build, you will need to configure your project to provide EAS Build with your npm token.
## Default npm configuration
By default, EAS Build uses a self-hosted npm cache that speeds up installing dependencies for all builds. Every EAS Build builder is configured with a **.npmrc** file for each platform:
### Android
```ini
registry=http://npm-cache-service.worker-infra-production.svc.cluster.local:4873
```
### iOS
```ini
registry=http://10.254.24.8:4873
```
## Private packages published to npm
If your project is using private packages published to npm, you need to provide EAS Build with [a read-only npm token](https://docs.npmjs.com/about-access-tokens) so that it can install your dependencies successfully.
The recommended way is to add the `NPM_TOKEN` secret to your account or project's secrets:
For more information on how to do that, see [secret environment variables](/build-reference/variables/#secrets-on-the-expo-website).
When EAS detects that the `NPM_TOKEN` environment variable is available during a build, it automatically creates the following **.npmrc**:
```ini .npmrc
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
registry=https://registry.npmjs.org/
```
However, this only happens when **.npmrc** is not in your project's root directory. If you already have this file, you need to update it manually.
You can verify if it worked by viewing build logs and looking for the **Prepare project** build phase:
## Packages published to a private registry
If you're using a private npm registry such as self-hosted [Verdaccio](https://verdaccio.org/), you will need to configure the **.npmrc** manually.
Create a **.npmrc** file in your project's root directory with the following contents:
```ini .npmrc
registry=__REPLACE_WITH_REGISTRY_URL__
```
If your registry requires authentication, you will need to provide the token. For example, if your registry URL is `https://registry.johndoe.com/`, then update the file with:
```ini .npmrc
//registry.johndoe.com/:_authToken=${NPM_TOKEN}
registry=https://registry.johndoe.com/
```
## Both private npm packages and private registry
> This is an advanced example.
Private npm packages are always [scoped](https://docs.npmjs.com/about-scopes#scopes-and-package-visibility). For example, if your npm username is `johndoe`, the private self-hosted registry URL is `https://registry.johndoe.com/`. If you want to install dependencies from both sources, create a **.npmrc** in your project's root directory with the following:
```ini .npmrc
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
@johndoe:registry=https://registry.npmjs.org/
registry=https://registry.johndoe.com/
```
## Submodules in private repositories
If you have a submodule in a private repository, you will need to initialize it by setting up an SSH key. For more information, see [submodules initialization](/build-reference/git-submodules/#submodules-initialization).
## Using Git submodules
Learn how to configure EAS Build to use git submodules.
When using the default Version Control Systems (VCS) workflow, the content of your working directory is uploaded to EAS Build as it is, including the content of Git submodules. However, if you are building on CI or have `cli.requireCommit` set to `true` in **eas.json** or have a submodule in a private repository, you will need to initialize it to avoid uploading empty directories.
## Submodules initialization
To initialize a submodule on EAS Build builder:
Step 1:
Create a [secret](/build-reference/variables/#using-secrets-in-environment-variables) with a base64 encoded private SSH key that has permission to access submodule repositories.
Step 2:
Add an [`eas-build-pre-install` npm hook](/build-reference/npm-hooks/) to check out those submodules, for example:
```bash eas-build-pre-install.sh
#!/usr/bin/env bash
mkdir -p ~/.ssh
# Real origin URL is lost during the packaging process, so if your
# submodules are defined using relative urls in .gitmodules then
# you need to restore it with:
#
# git remote set-url origin git@github.com:example/repo.git
# restore private key from env variable and generate public key
echo "$SSH_KEY_BASE64" | base64 -d > ~/.ssh/id_rsa
chmod 0600 ~/.ssh/id_rsa
ssh-keygen -y -f ~/.ssh/id_rsa > ~/.ssh/id_rsa.pub
# add your git provider to the list of known hosts
ssh-keyscan github.com >> ~/.ssh/known_hosts
git submodule update --init
```
## Using npm cache with Yarn 1 (Classic)
Learn how to use npm cache by overriding the registry in Yarn 1 (Classic).
By default, the EAS npm cache won't work with Yarn 1 (Classic) because **yarn.lock** files contain URLs to registries for every library. Yarn 1 does not provide any way to override it and Yarn team does not plan to support it in Yarn 1. However, this issue is fixed in Yarn 2+.
If you want to take advantage of the npm cache with Yarn 1, add the [`eas-build-pre-install` npm hook](/build-reference/npm-hooks/) in **package.json** to override the registry in the **yarn.lock**:
```json package.json
{
"scripts": {
"eas-build-pre-install": "bash -c \"[ ! -z \\\"$EAS_BUILD_NPM_CACHE_URL\\\" ] && sed -i -e \\\"s#https://registry.yarnpkg.com#$EAS_BUILD_NPM_CACHE_URL#g\\\" yarn.lock\" || true"
}
}
```
## Set up EAS Build with a monorepo
Learn how to set up EAS Build with a monorepo.
To set up EAS Build with a monorepo, you need to follow the standard process as described below:
- Run all EAS CLI commands from the root of the app directory. For example, if your project exists inside your git repository at **apps/my-app**, then run `eas build` from there.
- All files related to EAS Build, such as **eas.json** and **credentials.json**, should be in the root of the app directory. If you have multiple apps that use EAS Build in your monorepo, each app directory will have its own copy of these files.
- **If you are building a managed project in a monorepo**, see [Working with Monorepos](/guides/monorepos) guide.
- If your project needs additional setup beyond what is provided, add a `postinstall` step to **package.json** in your project that builds all necessary dependencies in other workspaces. For example:
```json package.json
{
"scripts": {
"postinstall": "cd ../.. && yarn build"
}
}
```
## Build APKs for Android Emulators and devices
Learn how to configure and install a .apk for Android Emulators and devices when using EAS Build.
The default file format used when building Android apps with EAS Build is an [Android App Bundle](https://developer.android.com/platform/technology/app-bundle) (AAB/**.aab**). This format is optimized for distribution to the Google Play Store. However, AABs can't be installed directly on your device. To install a build directly to your Android device or emulator, you need to build an [Android Package](https://en.wikipedia.org/wiki/Android_application_package) (APK/**.apk**) instead.
## Configuring a profile to build APKs
To generate an **.apk**, modify the [**eas.json**](/build/eas-json) by adding one of the following properties in a build profile:
- `developmentClient` to `true` (**default**)
- `distribution` to `internal`
- `android.buildType` to `apk`
- `android.gradleCommand` to `:app:assembleRelease`, `:app:assembleDebug` or any other gradle command that produces **.apk**
```json eas.json
{
"build": {
"preview": {
"android": {
"buildType": "apk"
}
},
"preview2": {
"android": {
"gradleCommand": ":app:assembleRelease"
}
},
"preview3": {
"developmentClient": true
},
"preview4": {
"distribution": "internal"
},
"production": {}
}
}
```
Now you can run your build with the following command:
```sh
$ eas build -p android --profile preview
```
Remember that you can name the profile whatever you like. We named the profile `preview`. However, you can call it `local`, `emulator`, or whatever makes the most sense for you.
## Installing your build
### Emulator (virtual device)
> If you haven't installed or run an Android Emulator before, follow the [Android Studio emulator guide](/workflow/android-studio-emulator) before proceeding.
Once your build is completed, the CLI will prompt you to automatically download and install it on the Android Emulator. When prompted, press Y to directly install it on the emulator.
In case you have multiple builds, you can also run the `eas build:run` command at any time to download a specific build and automatically install it on the Android Emulator:
```sh
$ eas build:run -p android
```
The command also shows a list of available builds of your project. You can select the build to install on the emulator from this list. Each build in the list has a build ID, the time elapsed since the build creation, the build number, the version number, and the git commit information. The list also displays invalid builds if a project has any.
For example, the image below lists the build of a project:
When the build's installation is complete, it will appear on the home screen. If it's a development build, open a terminal window and start the development server by running the command `npx expo start`.
#### Running the latest build
Pass the `--latest` flag to the `eas build:run` command to download and install the latest build on the Android Emulator:
```sh
$ eas build:run -p android --latest
```
### Physical device
#### Download directly to the device
- Once your build is completed, copy the URL to the APK from the build details page or the link provided when `eas build` is done.
- Send that URL to your device. Maybe by email? Up to you.
- Open the URL on your device, install the APK and run it.
#### Install with `adb`
- [Install adb](https://developer.android.com/studio/command-line/adb) if you don't have it installed already.
- Connect your device to your computer and [enable adb debugging on your device](https://developer.android.com/studio/command-line/adb#Enabling) if you haven't already.
- Once your build is completed, download the APK from the build details page or the link provided when `eas build` is done.
- Run `adb install path/to/the/file.apk`.
- Run the app on your device.
## Build for iOS Simulators
Learn how to configure and install build for iOS simulators when using EAS Build.
Running a build of your app on an iOS Simulator is useful. You can configure the build profile and install the build automatically on the simulator. This provides a standalone (independent of Expo Go) version of the app running without needing to deploy to TestFlight or even having an Apple Developer account.
## Configuring a profile to build for simulators
To install a build of your app on an iOS Simulator, modify the build profile in [**eas.json**](/build/eas-json/) and set the `ios.simulator` value to `true`:
```json eas.json
{
"build": {
"preview": {
"ios": {
"simulator": true
}
},
"production": {}
}
}
```
Now, execute the command as shown below to run the build:
```sh
$ eas build -p ios --profile preview
```
Remember that a profile can be named whatever you like. In the above example, it is called `preview`. However, you can call it `local`, `simulator`, or whatever makes the most sense.
## Installing build on the simulator
> If you haven't installed or run the iOS Simulator before, follow the [iOS Simulator guide](/workflow/ios-simulator) before proceeding.
Once your build is completed, the CLI will prompt you to automatically download and install it on the iOS Simulator. When prompted, press Y to directly install it on the simulator.
In case you have multiple builds, you can also run the `eas build:run` command at any time to download a specific build and automatically install it on the iOS Simulator:
```sh
$ eas build:run -p ios
```
The command also shows a list of available builds of your project. You can select the build to install on the simulator from this list. Each build in the list has a build ID, the time elapsed since the build creation, the build number, the version number, and the git commit information. The list also displays invalid builds if a project has any.
For example, the image below lists two previous builds of a project:
When the build's installation is complete, it will appear on the home screen. If it's a development build, open a terminal window and start the development server by running the command `npx expo start`.
### Running the latest build
Pass the `--latest` flag to the `eas build:run` command to download and install the latest build on the iOS Simulator:
```sh
$ eas build:run -p ios --latest
```
## App version management
Learn about different version types and how to manage them remotely or locally.
Android and iOS each expose two values to identify the version of an app: the version visible in stores (user-facing version) and the version visible only to developers (developer-facing build version). This guide explains how you can manage those versions remotely or locally.
Video Tutorial: [Automatic App Version Management](https://www.youtube.com/watch?v=Gk7RHDWsLsQ)
## App versions
In Expo projects, the following properties can be used to define app versions in the [app config](/workflow/configuration/) file.
| Property | Description |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`version` ](/versions/latest/config/app/#version) | The user-facing version visible in stores. On Android, it represents `versionName` name in **android/app/build.gradle**. On iOS, it represents `CFBundleShortVersionString` in **Info.plist**. |
| [`android.versionCode`](/versions/latest/config/app/#versioncode) | The developer-facing build version for Android. It represents `versionCode` in **android/app/build.gradle**. |
| [`ios.buildNumber`](/versions/latest/config/app/#buildnumber) | The developer-facing build version for iOS. It represents `CFBundleVersion` in **Info.plist**. |
### Using app versions in your app
To show the user-facing version inside your app, you can use [`Application.nativeApplicationVersion`](/versions/latest/sdk/application/#applicationnativeapplicationversion) from the `expo-application` library.
To show the developer-facing build version inside your app, you can use [`Application.nativeBuildVersion`](/versions/latest/sdk/application/#applicationnativebuildversion) from the `expo-application` library.
## Recommended workflow
### User-facing version
When you are doing a production release, the user-facing version should be explicitly set and updated by you. You can update the `version` property in app config when production build is submitted to the app stores. This also applies if your project uses `expo-updates` with an automatic runtime version policy. This marks the beginning of a new development cycle for a new version of your app. Learn more about [deployment patterns](/eas-update/deployment-patterns).
### Developer-facing build version
For developer-facing build version, you can set them to autoincrement on every build. This will help you avoid making manual changes to the project every time you upload a new archive on Play Store testing channels or TestFlight. One common cause for app store rejections is submitting a build with a duplicate version number. It happens when a developer forgets to increment the developer-facing build version number before creating a new build.
EAS Build can help manage developer-facing build versions automatically by incrementing these versions for you if you opt into using the [`remote` version source](#remote-version-source), which is the recommended behaviour. Optionally, you can choose to use a `local` app version source, which means you control versions manually in their respective config files.
## Remote version source
> **warning** The `remote` version source is the recommended behavior from EAS CLI version `12.0.0`. If you are using an older version of the EAS CLI, `local` is the default.
EAS servers can store and manage your app's developer-facing build version (`android.versionCode` and `ios.buildNumber`) remotely. To enable it, you need to set `cli.appVersionSource` to `remote` in **eas.json**. Then, under the `production` build profile, you can set the `autoIncrement` property to `true`.
```json eas.json
{
"cli": {
"appVersionSource": "remote"
},
"build": {
"development": {
},
"preview": {
},
"production": {
"autoIncrement": true
}
}
}
```
The remote version is initialized with the value from the local project. For example, if you have `android.versionCode` set to `1` in app config, when you create a new build using the remote version source, it will auto increment to `2`. However, if you do not have build versions set in your app config, the remote version will initialize with `1` when the first build is created.
When the `remote` version property is enabled inside **eas.json**, the build version values stored in app config are ignored and not updated when the version is incremented remotely. The remote version source values are set on the native project when running a build, which is considered the source of truth for these values. You can safely remove these values from your app config.
### Syncing already defined versions to remote
There are different scenarios where you already have versions set up for your project and want to increment from those versions when you create a new EAS Build. However, these existing versions might not be synced remotely with EAS. Some of these scenarios are:
- You have already published your app in the app stores and want to continue using the same version numbers.
- EAS CLI is not able to detect what version the app is on.
- For any other reason, you have versions explicitly set, such as inside your app config.
In these scenarios, you can sync the current version to EAS Build using the EAS CLI using the following steps:
- In the terminal window, run the following command:
```sh
$ eas build:version:set
```
- Select the platform (Android or iOS) when prompted.
- When prompted **Do you want to set app version source to remote now?**, select **yes**. This will set the `cli.appVersionSource` to `remote` in **eas.json**.
- When prompted **What version would you like to initialize it with?**, enter the last version number that you have set in the app stores.
After these steps, the app versions will be synced to EAS Build remotely. You can now set `build.production.autoIncrement` to `true` in **eas.json**. When you create a new production build, the `versionCode` and `buildNumber` will be automatically incremented.
### Syncing versions from remote to local
To build your project locally in Android Studio or Xcode using the same version stored remotely on EAS, update your local project with the remote versions using the following command:
```sh
$ eas build:version:sync
```
### Limitations
- `eas build:version:sync` command on Android does not support bare projects with multiple flavors. However, the rest of the remote versioning functionality should work with all projects.
- `autoIncrement` does not support the `version` option.
- It's not supported if you are using EAS Update and runtime policy set to `"runtimeVersion": { "policy": "nativeVersion" }`. For similar behavior, use the `"appVersion"` policy instead.
## Local version source
> **warning** The `remote` version source as a recommended behavior has been introduced in `eas-cli` version `12.0.0`. If you are using an older version of the CLI, you **don't need to specify the version source explicitly to `local`**.
You can configure your project so that the source of truth for project versions is the local project source code itself. To do this, set `cli.appVersionSource` to `local` in your **eas.json**.
With this setup, EAS reads app version values and builds projects as they are. It doesn't write to the project. You can also enable auto incrementing versions locally by setting the `autoIncrement` option on a build profile.
```json eas.json
{
"cli": {
"appVersionSource": "local"
},
"build": {
"development": {
},
"preview": {
},
"production": {
"autoIncrement": true
}
}
}
```
In the case of [existing React Native projects](/bare/overview/), the values in native code take precedence. The libraries `expo-constants` and `expo-updates` read values from the app config file. If you rely on version values from a manifest, you should keep them in sync with native code. Keeping these values in sync is especially important if you are using EAS Update with the runtime policy set to `"runtimeVersion": { "policy": "nativeVersion" }`, because mismatched versions may result in the delivery of updates to the wrong version of an application. We recommend using [`expo-application`](/versions/latest/sdk/application/#constants) to read the version instead of depending on values from app config.
### Limitations
- With `autoIncrement`, you need to commit your changes on every build if you want the version change to persist. This can be difficult to coordinate when building on CI.
- For existing React Native projects with Gradle configuration that supports multiple flavors, EAS CLI is not able to read or modify the version, so `autoIncrement` option is not supported, and versions will not be listed in the build details page on [expo.dev](https://expo.dev).
## Troubleshoot build errors and crashes
A reference for troubleshooting build errors and crashes when using EAS Build.
When something goes wrong, it probably will go wrong in one of two following ways:
1. Your build will fail.
2. The build will succeed but encounter a runtime error, for example, it crashes or hangs when you run it.
All standard advice around [narrowing down the source of an error](https://expo.fyi/manual-debugging) applies here; this document provides information that may be useful on top of your typical troubleshooting processes and techniques. Troubleshooting is an art, and you might need to think creatively.
## Find the related error logs
Before you go further, you need to be sure that you have located the error message and read it. How you do this will be different depending on whether you're investigating a build failure or runtime error.
### Runtime errors
Common questions that fall under this category are: "my app runs well locally but crashes immediately when I run a build" or "my app works in Expo Go but hangs on the splash screen in my build". When your app builds successfully but crashes or hangs when you run it, this is considered a runtime error.
Refer to the ["Production errors" section of the debugging guide](/debugging/runtime-issues/#production-errors) to learn how to locate logs when your release builds are crashing at runtime.
If you can't find any useful information through this approach, try [narrowing down the source of the crash step by step](https://expo.fyi/manual-debugging).
### Build errors
Go to your build details page (find it on the [build dashboard](https://expo.dev/accounts/[account]/projects/[project]/builds) if you don't have it open already) and expand any failed build phases by clicking on them. Often, the earliest phase with errors will contain the most useful information and any subsequent failed phase will have cascaded from the first.
Regardless of the phase, **it's common to see log entries prefixed with `[stderr]`, but keep in mind that this doesn't necessarily mean those logs point to errors**; it's common for CLI tools to use [stderr]() to output warnings and other diagnostics.
For example, you might see something like this on your Android builds:
```sh
`[stderr] Note: /build/workingdir/build/app/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageModule.java uses or overrides a deprecated API.`
`[stderr] Note: Recompile with -Xlint:deprecation for details.`
```
While you may or may not be interested in following up on that warning, it is not the cause of your failed build. So how do you know which logs are truly responsible? If you are building a bare project, you will already be good at this. If you are building a [managed project](/archive/managed-vs-bare/), it may be tricky because you don't directly interact with the native code, only write JavaScript.
A good path forward is to **determine if the build failed due to a native or JavaScript error**. When your build fails due to a JavaScript build error, you will usually see something like this:
```sh
`❌ Metro encountered an error:`
`Unable to resolve module ./src/Routes from /Users/expo/workingdir/build/App.js`
```
This particular error means that the app is importing **./src/Routes** and it is not found. The cause could be that the filename case is different in Git than the developer's filesystem (for example, **routes.js** in Git instead of **Routes.js**), or maybe the project has a build step and it wasn't set up to run on EAS Build. In this case, it turns out that in this case **./src/Routes** was intended to import **./src/Routes/index.js**, but that path was accidentally excluded in the developer's **.gitignore**.
It's important to note that with iOS builds the build details page only displays an abridged version of the logs because the full output from `xcodebuild` can be in the order of 10MB. Sometimes it's necessary to open the full Xcode logs to find the information that you need; for example, if the JavaScript build failed but you don't see any useful information on the build details page. To open the full Xcode logs, scroll to the bottom of the build details page when the build has been completed and either click to view or download them.
If you are working on a managed app and the build error is a native error rather than a JavaScript error, this is likely due to a [config plugin](/config-plugins/introduction/) or a dependency in your project. Keep an eye out in the logs for any new packages that you have added since your previous successful build. Run `npx expo-doctor` to determine that the versions of Expo SDK dependencies in your project are compatible with your Expo SDK version.
Armed with your error logs, you can often start to fix your build or search the [forums](https://chat.expo.dev/) and GitHub issues for related packages to dig deeper. Some common sources of problems are listed below.
Note: Are you using a monorepo?
---
Monorepos are incredibly useful but they do introduce their own set of problems. It's necessary to upload the entire monorepo to the EAS Build builders, set it up, and run the build.
EAS Build is more like a typical CI service in that we need the source code, rather than a compiled JavaScript bundle and manifest. EAS Build has first-class support for Yarn workspaces, and [your success may vary when using other monorepo tools](/build-reference/limitations).
For more information, see [Working with monorepos](/guides/monorepos).
---
Note: Out-of-memory (OOM) errors
---
If your build fails with "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)" in your Gradle logs, this is because the Node process responsible for bundling your app JavaScript was killed.
This can often be a sign that your app bundle is extremely large, which will make your overall app binary larger and lead to slow boot up times, especially on low-end Android devices. Sometimes the error can occur when large text files are treated as source code, for example, if you have a JavaScript file that contains a string of 1MB+ of HTML to load into a webview, or a similarly sized JSON file.
To determine how large your bundle is and to see a breakdown of where the size comes from, use [Expo Atlas](/guides/analyzing-bundles/).
To increase memory limits on your EAS Build builders, use [`large` resource class](/eas/json/#resourceclass) in your **eas.json**. See [Android-specific resource class](/build-reference/infrastructure/#android-build-server-configurations) and [iOS-specific resource class](/build-reference/infrastructure/#ios-build-server-configurations) for more information.
---
Note: None of the files exist error
---
When you run `eas build`, your project's files are uploaded to Expo's build servers. However, any file or directory mentioned in the **.gitignore** is **not uploaded**. This is intentional to prevent sensitive information, such as API keys, from being exposed in your app's code.
If your project imports a file listed in **.gitignore**, the build will fail with a `None of these files exist` error. There are different ways you can resolve this error:
- Remove the import statement for the ignored file and test your project. If your project functions as expected, that import statement may have been outdated or unused.
- Remove any files or directories Metro was unable to resolve from your **.gitignore**. However, this poses a security risk since any sensitive information included in these files will now be available in your project's source code and Git commit history.
- Encode the file with `base64`, save that string as secrets, and create the file in an EAS Build hook. See [How can I upload files to EAS Build if they are gitignored?](https://expo.fyi/eas-build-archive.md#how-can-i-upload-files-to-eas-build-if-they-are-gitignored) for more information.
- Refactor your source code to avoid importing sensitive files on the client side. If a file is an auto-generated code from a third-party provider and that provider has automatically listed files in your **.gitignore**, then that file probably contains sensitive information. You should not include it on the client side. During app development, ensure you follow secure practices, such as using environment variables or serving them through your backend. See [Using secrets in environment variables](/build-reference/variables/#using-secrets-in-environment-variables) for more information.
---
## Verify that your JavaScript bundles locally
When a build fails with `Task :app:bundleReleaseJsAndAssets FAILED` (Android) or `Metro encountered an error` (iOS), it means Metro bundler was unable to bundle the app's JavaScript code while trying to embed it in your app's binary. This error message is usually followed by a syntax error or other details about why bundling failed. Unfortunately, a standard React Native project is configured to perform this step late in the Gradle/Xcode build step, meaning it can take a while to see this error.
You can build the production bundle locally by running `npx expo export` to bypass all of the other build steps so you can see this error much more quickly. Run this command repeatedly, resolving any syntax errors or other issues uncovered until the bundle builds successfully. Then try your EAS Build again.
## Verify that your project builds and runs locally
If the logs weren't enough to immediately help you understand and fix the root cause, it's time to try to reproduce the issue locally. If your project builds and runs locally in release mode then it will also build on EAS Build, provided that the following are all true:
- Relevant [Build tool versions](/build/eas-json#configuring-your-build-tools) (for example, Xcode, Node.js, npm, Yarn) are the same in both environments.
- Relevant [environment variables](/build-reference/variables) are the same in both environments.
- The [archive](https://expo.fyi/eas-build-archive) that is uploaded to EAS Build includes the same relevant source files.
You can verify that your project builds on your local machine with the `npx expo run:android` and `npx expo run:ios` commands, with variant/configuration flags set to release to most faithfully reproduce what executes on EAS Build. For more information, see [Android build process](/build-reference/android-builds) and [iOS build process](/build-reference/ios-builds).
```sh
$ npx expo run:android --variant release
$ npx expo run:ios --configuration Release
```
> If use [CNG](/workflow/continuous-native-generation/), these commands will run `npx expo prebuild` to generate native projects to compile them.You likely want to [clean up the changes](https://expo.fyi/prebuild-cleanup) once you are done troubleshooting, unless you want to start managing these projects directly instead of generating them on demand.
>
>
> You can alternatively run a local build with `eas build --local` — this command will run a
> series of steps that is as close as it can be to what runs remotely on the hosted EAS Build
> service. It will copy your project to a temporary directory and make any necessary changes there.
> [Learn how to set this up and use it for
> debugging](/build-reference/local-builds.mdx#using-local-builds-for-debugging).
If your native toolchains are installed correctly and you are unable to build and run your project in release mode on your local machine, it will not build on EAS Build. Fix the issues locally, then try again on EAS Build. The other advice in this doc may be useful to help you resolve the issue locally, but often this requires some knowledge of native tooling or judicious application of Google, Stack Overflow, and GitHub Issues.
Note: Don't have Xcode and Android Studio set up on your machine?
---
**If you do not have native toolchains installed locally**, for example, because you do not have an Apple computer and therefore cannot build an iOS app on your machine, it can be trickier to get to the bottom of build errors. The feedback loop of making small changes locally and then seeing the result on EAS Build is slower than doing the same steps locally because the EAS Build builder must set up its environment, download your project, and install dependencies before starting the build.
If you are willing and able to set up the appropriate native tools, then refer to the [React Native environment setup guide](https://reactnative.dev/docs/environment-setup).
---
Note: My app builds locally, but not on EAS Build
---
By default, EAS Build follows a relatively straightforward process for building your app for ([Android](/build-reference/android-builds) or [iOS](/build-reference/ios-builds)). If `npx expo run:android --variant release` and `npx expo run:ios --configuration Release` work locally, but your builds fail, then it's time to narrow down what configuration exists on your machine that hasn't been set up for your project on EAS Build yet.
- Do a fresh `git clone` of your project to a new directory and get it running, ideally on a different machine. Pay attention to each of the steps that are needed and verify that they are also configured for EAS Build.
- Check that your [environment variables](/guides/environment-variables) are properly configured.
- Verify that versions of Node.js, npm, Yarn, Xcode, Java, and other tools are the same in both environments.
- Ensure that the [archive you are uploading to EAS Build](https://expo.fyi/eas-build-archive) includes the same relevant source files.
---
Note: Why does my production app not match my development app?
---
You can test how the JS part of your app will run in production by starting it with [`npx expo start --no-dev`](/workflow/development-mode/#production-mode). This tells the bundler to minify JavaScript before serving it, most notably stripping code protected by the `__DEV__` boolean. This will remove most of the logging, HMR, Fast Refresh functionality, and make debugging a bit harder, but you can iterate on the production bundle faster this way.
---
## Still having trouble?
This guide is far from being comprehensive, and depending on your level of experience you might still be struggling to get your app working.
If you have followed the advice here, you're now in a good position to describe your issue to other developers and get some help.
### How to ask a good question
Join us on Discord and Forums to ask for help from the community and the Expo team. The Expo team does our best to respond to high quality and well-articulated questions and issues, but responses are not guaranteed unless you are signed up for a [support plan](https://expo.dev/support-terms#target-response-time-guidelines-for-subscriptions). To ensure that an Expo team member sees your question, you can file a ticket at [expo.dev/contact](https://expo.dev/contact).
When you ask for troubleshooting help, be sure to share the following information:
- **A link to your build page**. This can only be accessed by your team or Expo employees. If you'd like to share it more publicly, take a screenshot. If you'd like to share it more privately, send an email to secure@expo.dev and mention that in your help request on chat or forums. If you are performing this build locally with `eas build --local`, you can omit this, but do mention this fact.
- **Error logs**. Anything that you suspect may be related to your build or runtime error. If you can't provide this, explain why not.
- **Minimal reproducible example or a link to your repository**. The quickest way to get a solution to your problem is to ensure that other developers can reproduce it. If you have ever worked on a team, you know this from experience. In many cases, if you can't provide a reproducible example then it may not be possible to help you, and at best the back-and-forth process of asking and answering questions will be an inefficient use of time. Learn more about how to create a reproducible example in the [manual debugging guide](https://expo.fyi/manual-debugging) and Stack Overflow's [Minimal Viable Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) guide.
Try to be clear, precise, and helpful. General guidance provided by Stack Overflow's [How to ask a good question](https://stackoverflow.com/help/how-to-ask) guide applies.
## Install app variants on the same device
Learn how to install multiple variants of an app on the same device.
When creating [development, preview, and production builds](/build/eas-json/#common-use-cases), installing these build variants simultaneously on the same device is common. This allows working in development, previewing the next version of the app, and running the production version on a device without needing to uninstall and reinstall the app.
This guide provides the steps required to configure multiple (development and production) variants to install and use them on the same device.
## Prerequisites
To have multiple variants of an app installed on your device, each variant must have a unique [Application ID (Android)](/versions/latest/config/app/#package) or [Bundle Identifier (iOS)](/versions/latest/config/app/#bundleidentifier).
## Configure development and production variants
You have created a project using Expo tooling, and now you want to create a development and a production build. Your project's **app.json** may have the following configuration:
```json app.json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"ios": {
"bundleIdentifier": "com.myapp"
},
"android": {
"package": "com.myapp"
}
}
}
```
If your project has EAS Build configured, the **eas.json** also has a similar configuration as shown below:
```json eas.json
{
"build": {
"development": {
"developmentClient": true
},
"production": {}
}
}
```
### Convert app.json to app.config.js
To have multiple variants of the app installed on the same device, rename the **app.json** to **app.config.js** and export the configuration as shown below:
```js app.config.js
export default {
name: 'MyApp',
slug: 'my-app',
ios: {
bundleIdentifier: 'com.myapp',
},
android: {
package: 'com.myapp',
},
};
```
In **app.config.js**, add an environment variable called `IS_DEV` to switch the `android.package` and `ios.bundleIdentifier` for each variant based on the variable:
```js app.config.js
const IS_DEV = process.env.APP_VARIANT === 'development';
export default {
name: IS_DEV ? 'MyApp (Dev)' : 'MyApp',
slug: 'my-app',
ios: {
bundleIdentifier: IS_DEV ? 'com.myapp.dev' : 'com.myapp',
},
android: {
package: IS_DEV ? 'com.myapp.dev' : 'com.myapp',
}
};
```
In the above example, the environment variable `IS_DEV` is used to differentiate between the development and production environment. Based on its value, the different Application IDs or Bundle Identifiers are set for each variant.
Note: Additional app variant customizations
---
You can customize other aspects of your app on a per-variant basis. You can swap any configuration that you used previously in **app.json** using the same approach as above.
**Examples:**
- If you are using a library that requires you to register your application identifier with an external service to use its SDK, such as Google Maps or Firebase Cloud Messaging (FCM), you'll need to have a separate configuration for that API for the `android.package` and `ios.bundleIdentifier`.
- If you're using [development builds](/develop/development-builds/introduction/), you can configure the `expo-dev-client` plugin to disable the app scheme used by Expo CLI and EAS Update QR codes in non-development builds. This ensures that those URLs will always launch the development build, regardless of your device's defaults:
```js app.config.js
plugins: [
[
'expo-dev-client',
{
addGeneratedScheme: !!IS_DEV,
},
],
],
```
---
### Configuration for EAS Build
In **eas.json**, set the `APP_VARIANT` environment variable to run builds with the **development** profile by using the `env` property:
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"env": {
"APP_VARIANT": "development"
}
},
"production": {}
}
}
```
Now, when you run `eas build --profile development`, the environment variable `APP_VARIANT` is set to `development` when evaluating **app.config.js** both locally and on the EAS Build builder.
### Using the development server
When you start your development server, you'll need to run `APP_VARIANT=development npx expo start` (or the platform equivalent if you use Windows).
A shortcut for this is to add the following script to your **package.json**:
```json package.json
{
"scripts": {
"dev": "APP_VARIANT=development npx expo start"
}
}
```
### Using production variant
When you run `eas build --profile production` the `APP_VARIANT` variable environment is not set, and the build runs as the production variant.
> **Note**: If you use EAS Update to publish JavaScript updates of your app, you should be cautious to set the correct environment variables for the app variant that you are publishing for when you run the `eas update` command. See the EAS Build [Environment variables and secrets](/build/updates) for more information.
### In bare project
#### Android
In **android/app/build.gradle**, create a separate flavor for every build profile from **eas.json** that you want to build.
```groovy android/app/build.gradle
android {
flavorDimensions "env"
productFlavors {
production {
dimension "env"
applicationId 'com.myapp'
}
development {
dimension "env"
applicationId 'com.myapp.dev'
}
}
}
```
> **Note**: Currently, EAS CLI supports only the `applicationId` field. If you use `applicationIdSuffix` inside `productFlavors` or `buildTypes` sections then this value will not be detected correctly.
Assign Android flavors to EAS Build profiles by specifying a `gradleCommand` in the **eas.json**:
```json eas.json
{
"build": {
"development": {
"android": {
"gradleCommand": ":app:assembleDevelopmentDebug"
}
},
"production": {
"android": {
"gradleCommand": ":app:bundleProductionRelease"
}
}
}
}
```
By default, every flavor can be built in either debug or release mode. If you want to restrict some flavor to a specific mode, see the snippet below, and modify **build.gradle**.
```groovy android/app/build.gradle
android {
variantFilter { variant ->
def validVariants = [
["production", "release"],
["development", "debug"],
]
def buildTypeName = variant.buildType*.name
def flavorName = variant.flavors*.name
def isValid = validVariants.any { flavorName.contains(it[0]) && buildTypeName.contains(it[1]) }
if (!isValid) {
setIgnore(true)
}
}
}
```
The rest of the configuration at this point is not specific to EAS, it's the same as it would be for any Android project with flavors. There are a few common configurations that you might want to apply to your project:
- To change the name of the app built with the development profile, create a **android/app/src/development/res/value/strings.xml** file:
```xml android/app/src/development/res/value/strings.xml
MyApp - Dev
```
- To change the icon of the app built with the development profile, create **android/app/src/development/res/mipmap-\*** directories with appropriate assets (you can copy them from **android/app/src/main/res** and replace the icon files).
- To specify **google-services.json** for a specific flavor, put it in the **android/app/src/{flavor}/google-services.json** file.
- To configure sentry, add `project.ext.sentryCli = [ flavorAware: true ]` to **android/app/build.gradle** and name your properties file **android/sentry-\{flavor\}-\{buildType\}.properties** (for example, **android/sentry-production-release.properties**)
#### iOS
Assign a different `scheme` to every build profile in **eas.json**:
```json eas.json
{
"build": {
"development": {
"ios": {
"buildConfiguration": "Debug",
"scheme": "myapp-dev"
}
},
"production": {
"ios": {
"buildConfiguration": "Release",
"scheme": "myapp"
}
}
}
}
```
**Podfile** should have a target defined like this:
```ruby Podfile
target 'myapp' do
# @hide ... #
# @end #
end
```
Replace it with an abstract target, where the common configuration can be copied from the old target:
```ruby Podfile
abstract_target 'common' do
# put common target configuration here
target 'myapp' do
end
target 'myapp-dev' do
end
end
```
Open project in Xcode, click on the project name in the navigation panel, right click on the existing target, and click "Duplicate":
Rename the target to something more meaningful, for example, `myapp copy` -> `myapp-dev`.
Configure a scheme for the new target:
- Go to `Product` -> `Scheme` -> `Manage schemes`.
- Find scheme `myapp copy` on the list.
- Change scheme name `myapp copy` -> `myapp-dev`.
- By default, the new scheme should be marked as shared, but Xcode does not create `.xcscheme` files. To fix that, uncheck the "Shared" checkbox and check it again, after that new `.xcscheme` file should show up in the **ios/myapp.xcodeproj/xcshareddata/xcschemes** directory.
By default, the newly created target has separate **Info.plist** file (in the above example, it's **ios/myapp copy-Info.plist**). To simplify your project we recommend using the same file for all targets:
- Delete **./ios/myapp copy-Info.plist**.
- Click on the new target.
- Go to `Build Settings` tab.
- Find `Packaging` section.
- Change **Info.plist** value - **myapp copy-Info.plist** -> **myapp/Info.plist**.
- Change `Product Bundle Identifier`.
To change the display name:
- Open **Info.plist** and add key `Bundle display name` with value `$(DISPLAY_NAME)`.
- Open `Build Settings` for both targets and find `User-Defined` section.
- Add key `DISPLAY_NAME` with the name you want to use for that target.
To change the app icon:
- Create a new image set (you can create it from the existing image set for the current icon, it's usually named `AppIcon`)
- Open `Build Settings` for the target that you want to change icon.
- Find `Asset Catalog Compiler - Options` section.
- Change `Primary App Icon Set Name` to the name of the new image set.
## iOS capabilities
Learn about built-in iOS capabilities supported in EAS Build and how to enable or disable them.
When you make a change to your iOS entitlements, this change needs to be updated remotely on Apple's servers before making a production build. EAS Build automatically synchronizes capabilities on the Apple Developer Console with your local entitlements configuration when you run `eas build`. Capabilities are web services provided by Apple, think of them like AWS or Firebase services.
> This feature can be disabled with `EXPO_NO_CAPABILITY_SYNC=1 eas build`
## Entitlements
In an Expo app, the entitlements are read from the introspected app config. To edit them, see the [`ios.entitlements`](/versions/latest/config/app/#entitlements) field in your app config file. You can see your introspected config by running `npx expo config --type introspect` in your project and then look for the `ios.entitlements` object for the results.
In a bare React Native app, the entitlements are read from your **ios/\*\*/\*.entitlements** file.
## Enabling
If a supported entitlement is present in the entitlements file, then running `eas build` will enable it on Apple Developer Console. If the capability is already enabled, then EAS Build will skip it.
## Disabling
If a capability is enabled for your app remotely, but not present in the native entitlements file, then running `eas build` will automatically disable it.
## Supported capabilities
EAS Build will only enable capabilities that it has built-in support for, any unsupported entitlements must be manually enabled via [Apple Developer Console][apple-dev-console].
| Support | Capability | Entitlement string |
| ----------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| | Access Wi-Fi Information | `com.apple.developer.networking.wifi-info` |
| | App Attest | `com.apple.developer.devicecheck.appattest-environment` |
| | App Groups | `com.apple.security.application-groups` |
| | Apple Pay Later Merchandising | `com.apple.developer.pay-later-merchandising` |
| | Apple Pay Payment Processing | `com.apple.developer.in-app-payments` |
| | Associated Domains | `com.apple.developer.associated-domains` |
| | AutoFill Credential Provider | `com.apple.developer.authentication-services.autofill-credential-provider` |
| | ClassKit | `com.apple.developer.ClassKit-environment` |
| | Communicates with Drivers | `com.apple.developer.driverkit.communicates-with-drivers` |
| | Communication Notifications | `com.apple.developer.usernotifications.communication` |
| | Custom Network Protocol | `com.apple.developer.networking.custom-protocol` |
| | Data Protection | `com.apple.developer.default-data-protection` |
| | DriverKit Allow Third Party UserClients | `com.apple.developer.driverkit.allow-third-party-userclients` |
| | DriverKit Family Audio (development) | `com.apple.developer.driverkit.family.audio` |
| | DriverKit Family HID Device (development) | `com.apple.developer.driverkit.family.hid.device` |
| | DriverKit Family HID EventService (development) | `com.apple.developer.driverkit.family.hid.eventservice` |
| | DriverKit Family Networking (development) | `com.apple.developer.driverkit.family.networking` |
| | DriverKit Family SCSIController (development) | `com.apple.developer.driverkit.family.scsicontroller` |
| | DriverKit Family Serial (development) | `com.apple.developer.driverkit.family.serial` |
| | DriverKit Transport HID (development) | `com.apple.developer.driverkit.transport.hid` |
| | DriverKit USB Transport (development) | `com.apple.developer.driverkit.transport.usb` |
| | DriverKit for Development | `com.apple.developer.driverkit` |
| | Extended Virtual Address Space | `com.apple.developer.kernel.extended-virtual-addressing` |
| | Family Controls | `com.apple.developer.family-controls` |
| | FileProvider TestingMode | `com.apple.developer.fileprovider.testing-mode` |
| | Fonts | `com.apple.developer.user-fonts` |
| | Group Activities | `com.apple.developer.group-session` |
| | HealthKit | `com.apple.developer.healthkit` |
| | HomeKit | `com.apple.developer.homekit` |
| | Hotspot | `com.apple.developer.networking.HotspotConfiguration` |
| | Increased Memory Limit | `com.apple.developer.kernel.increased-memory-limit` |
| | Inter-App Audio | `inter-app-audio` |
| | Journaling Suggestions | `com.apple.developer.journal.allow` |
| | Low Latency HLS | `com.apple.developer.coremedia.hls.low-latency` |
| | MDM Managed Associated Domains | `com.apple.developer.associated-domains.mdm-managed` |
| | Managed App Installation UI | `com.apple.developer.managed-app-distribution.install-ui` |
| | Maps | `com.apple.developer.maps` |
| | Matter Allow Setup Payload | `com.apple.developer.matter.allow-setup-payload` |
| | Media Device Discovery | `com.apple.developer.media-device-discovery-extension` |
| | Messages Collaboration | `com.apple.developer.shared-with-you.collaboration` |
| | Multipath | `com.apple.developer.networking.multipath` |
| | NFC Tag Reading | `com.apple.developer.nfc.readersession.formats` |
| | Network Extensions | `com.apple.developer.networking.networkextension` |
| | 5G Network Slicing | `com.apple.developer.networking.slicing.appcategory` or `com.apple.developer.networking.slicing.trafficcategory` |
| | On Demand Install Capable for App Clip Extensions | `com.apple.developer.on-demand-install-capable` |
| | Personal VPN | `com.apple.developer.networking.vpn.api` |
| | Push Notifications | `aps-environment` |
| | Push to Talk | `com.apple.developer.push-to-talk` |
| | Recalibrate Estimates | `com.apple.developer.healthkit.recalibrate-estimates` |
| | Sensitive Content Analysis | `com.apple.developer.sensitivecontentanalysis.client` |
| | Shallow Depth and Pressure | `com.apple.developer.submerged-shallow-depth-and-pressure` |
| | Shared with You | `com.apple.developer.shared-with-you` |
| | Sign In with Apple | `com.apple.developer.applesignin` |
| | SiriKit | `com.apple.developer.siri` |
| | System Extension | `com.apple.developer.system-extension.install` |
| | Tap to Pay on iPhone | `com.apple.developer.proximity-reader.payment.acceptance` |
| | Tap to Present ID on iPhone (Display Only) | `com.apple.developer.proximity-reader.identity.display` |
| | TV Services | `com.apple.developer.user-management` |
| | Time Sensitive Notifications | `com.apple.developer.usernotifications.time-sensitive` |
| | Wallet | `com.apple.developer.pass-type-identifiers` |
| | WeatherKit | `com.apple.developer.weatherkit` |
| | Wireless Accessory Configuration | `com.apple.external-accessory.wireless-configuration` |
| | iCloud | `com.apple.developer.icloud-container-identifiers` |
| | HLS Interstitial Previews | Unknown |
The unsupported capabilities either don't support iOS, or they don't have a corresponding entitlement value. Here is a list of all of the [official Apple capabilities](https://developer.apple.com/help/account/reference/supported-capabilities-ios).
## Capability identifiers
Merchant IDs, App Groups, and CloudKit Containers can all be automatically registered and assigned to your app. These assignments require Apple cookies authentication (running locally) as the official App Store Connect API does not support these operations.
## Debugging iOS capabilities
You can run `EXPO_DEBUG=1 eas build` to get more detailed logs regarding the capability syncing.
If you have trouble using this feature, you can disable it with the environment variable `EXPO_NO_CAPABILITY_SYNC=1`.
To see all of the currently enabled capabilities, visit [Apple Developer Console][apple-dev-console], and find the bundle identifier matching your app, if you click on it you should see a list of all the currently enabled capabilities.
## Manual setup
There are two ways to manually enable Apple capabilities, both systems will require any existing Apple provisioning profiles to be regenerated.
### Xcode
> Preferred method for projects that do **not** use [Expo Prebuild](/workflow/prebuild) to continuously generate the native **android** and **ios** directories.
1. Open the **ios** directory in Xcode with `xed ios`. If you don't have an **ios** directory, run `npx expo prebuild -p ios` to generate one.
2. Then follow the steps mentioned in [Add a capability][apple-enable-capability].
### Apple Developer Console
First step is to add the respective key/value pairs to your **ios/[app]/[app].entitlements** (or more specific entitlements file for multi-target apps). You can refer to [Supported Capabilities](#supported-capabilities) to determine which entitlements keys should be added.
1. Log into [Apple Developer Console][apple-dev-console]. Click on "Certificates, IDs & Profiles", then navigate to "Identifiers" page.
2. Choose the bundle identifier that matches your app's bundle identifier.
3. Scroll down and enable a capability, some capabilities may require extra setup.
4. Scroll to the top and press "Save". You will see a dialog that says "Modify App Capabilities", press "Confirm" to continue. You will need to regenerate any provisioning profiles that use this bundle identifier before they'll be valid for building a code signed production **.ipa**.
If adding capabilities process has not been done correctly then your iOS native build will fail with an error similar to:
```text
❌ error: Provisioning profile "*[expo] app.bacon.hello AppStore ..." doesn't support the Associated Domains capability.
❌ error: Provisioning profile "*[expo] app.bacon.hello AppStore ..." doesn't include the com.apple.developer.associated-domains entitlement.
```
[apple-enable-capability]: https://help.apple.com/xcode/mac/current/#/dev88ff319e7
[apple-dev-console]: https://developer.apple.com/account/resources/identifiers/list
## Run EAS Build locally with local flag
Learn how to use EAS Build locally on your machine or a custom infrastructure using the --local flag.
You can run the same build process that is typically run on the EAS Build servers directly on your machine by using the `eas build --local` flag. This is a useful way to debug build failures that are happening on your cloud builds, which you may not be able to reproduce without running the same set of steps.
```sh
$ eas build --platform android --local
$ eas build --platform ios --local
```
## Prerequisites
You need to be authenticated with Expo:
- Run `eas login`
- Alternatively, set `EXPO_TOKEN` [using token-based authentication](/accounts/programmatic-access)
## Use cases for local builds
- [Debugging](#use-local-builds-for-debugging) build failures on EAS servers.
- Company policies that restrict the use of third-party CI/CD services. With local builds, the entire process runs on your infrastructure and the only communication with EAS servers is:
- to make sure project `@account/slug` exists
- if you are using managed credentials to download them
## Use local builds for debugging
If you encounter build failures on EAS servers and you're unable to determine the cause from inspecting the logs, you may find it helpful to debug the issue locally. To simplify that process we support several environment variables to configure the local build process.
- `EAS_LOCAL_BUILD_SKIP_CLEANUP=1` - Set this to disable cleaning up the working directory after the build process is finished.
- `EAS_LOCAL_BUILD_WORKINGDIR` - Specify the working directory for the build process, by default it's somewhere (it's platform dependent) in **/tmp** directory.
- `EAS_LOCAL_BUILD_ARTIFACTS_DIR` - The directory where artifacts are copied after a successful build. By default, these files are copied to the current directory, which may be undesirable if you are running many consecutive builds.
If you use `EAS_LOCAL_BUILD_SKIP_CLEANUP` and `EAS_LOCAL_BUILD_WORKINGDIR` for iOS builds you should be able to inspect the contents of the `logs` subdirectory of the working directory to read your Xcode logs.
## Limitations
Some of the options available for cloud builds are not available locally. Limitations you should be aware of:
- You can only build for a specific platform (option `all` is disabled).
- Customizing versions of software is not supported, fields `node`, `yarn`, `fastlane`, `cocoapods`, `ndk`, `image` in **eas.json** are ignored.
- Caching is not supported.
- EAS Secrets are not supported (set them in your environment locally instead).
- You are responsible for making sure that the environment has all the necessary tools installed:
- Node.js/Yarn/npm
- fastlane (iOS only)
- CocoaPods (iOS only)
- Android SDK and NDK
- On Windows, you can use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) for local EAS Builds. However, we do not officially test against this platform and do not support Windows for local builds (macOS and Linux are supported).
## App compilation for development and production builds locally
To compile your app locally for development with Expo CLI, use `npx expo run:android` or `npx expo run:ios` commands instead. If you use [Continuous Native Generation](/workflow/continuous-native-generation), you can also run [prebuild](/workflow/prebuild) to generate your **android** and **ios** directories and then proceed to open the projects in the respective IDEs and build them like any native project. For more details, see:
To create a production build locally, you need Android Studio and Xcode installed on your computer. See the following guide for more information:
With any of the above approaches, you'll be following procedures which are different from creating a build on the cloud with EAS Build — that is what the `eas build --local` flag is for.
## Cache dependencies
Learn how to speed up your builds by caching dependencies.
Before a build job can begin compiling your project, all project dependencies need to be available on disk. The longer it takes to acquire the dependencies, the more you need to wait for your build to complete — so caching dependencies is an important part of speeding up your builds.
> We're actively working on improving caching and other aspects of the build process to make builds reliably fast.
## Custom caching
The `cache` field on build profiles in [eas.json](/build/eas-json) can be used to configure caching for specific files and directories. Specified files will be saved to persistent storage after a successful build and restored on subsequent builds after the JavaScript dependencies are installed. Restoring does not overwrite existing files. Changing the `cache.key` value will invalidate the cache. Changing any other property of the `cache` object will also invalidate the cache.
## JavaScript dependencies
EAS Build runs an npm cache server that can speed up downloading JavaScript dependencies for your build jobs. By default, projects using npm or Yarn 2+ will use the cache. However, Yarn 1 (Classic) requires that you apply this [workaround](/build-reference/npm-cache-with-yarn) to use the cache in your project's **package.json**.
To disable using our npm cache server for your builds set the `EAS_BUILD_DISABLE_NPM_CACHE` env variable value to `"1"` in **eas.json**.
```json eas.json
{
"build": {
"production": {
"env": {
"EAS_BUILD_DISABLE_NPM_CACHE": "1"
}
}
}
}
```
## Android dependencies
EAS Build runs a Maven cache server that can speed up downloading Android dependencies for your build jobs.
Currently, we are caching:
- `maven-central` - [https://repo1.maven.org/maven2/](https://repo1.maven.org/maven2/)
- `google` - [https://maven.google.com/](https://maven.google.com/)
- `jcenter` - [https://jcenter.bintray.com/](https://jcenter.bintray.com/)
- `plugins` - [https://plugins.gradle.org/m2/](https://plugins.gradle.org/m2/)
To disable using our Maven cache server for your builds set the `EAS_BUILD_DISABLE_MAVEN_CACHE` env variable value to `"1"` in **eas.json**.
```json eas.json
{
"build": {
"production": {
"env": {
"EAS_BUILD_DISABLE_MAVEN_CACHE": "1"
}
}
}
}
```
## iOS dependencies
EAS Build serves most CocoaPods artifacts from a cache server. This improves the consistency of `pod install` times and generally improves speed. The cache will be bypassed automatically if you provide your own **.netrc** or **.curlrc** files.
To disable using our CocoaPods cache server for your builds set the `EAS_BUILD_DISABLE_COCOAPODS_CACHE` env variable value to `"1"` in **eas.json**.
```json eas.json
{
"build": {
"production": {
"env": {
"EAS_BUILD_DISABLE_COCOAPODS_CACHE": "1"
}
}
}
}
```
It is typical to not have your project **Podfile.lock** committed to source control when using [prebuild](/workflow/prebuild) to generate your **ios** directory [remotely at build time](/build-reference/ios-builds).
It can be useful to cache your **Podfile.lock** to have deterministic builds, but the tradeoff in this case is that, because you don't use the lockfile during local development, your ability to determine when a change is needed and to update specific dependencies is limited.
If you cache this file, you may occasionally end up with build errors that require clearing the cache.
To cache **Podfile.lock**, add **./ios/Podfile.lock** to the `cache.paths` list in your build profile in **eas.json**.
```json eas.json
{
"build": {
"production": {
"cache": {
"paths": ["./ios/Podfile.lock"]
}
}
}
}
```
## Android build process
Learn how an Android project is built on EAS Build.
This page describes the process of building Android projects with EAS Build. You may want to read this if you are interested in the implementation details of the build service.
## Build process
Let's take a closer look at the steps for building Android projects with EAS Build. We'll first run some steps on your local machine to prepare the project, and then we'll build the project on a remote service.
### Local steps
The first phase happens on your computer. EAS CLI is in charge of completing the following steps:
1. If `cli.requireCommit` is set to `true` in **eas.json**, check if the git index is clean - this means that there aren't any uncommitted changes. If it's not clean, EAS CLI will provide an option to commit local changes for you or abort the build process.
1. Prepare the credentials needed for the build unless `builds.android.PROFILE_NAME.withoutCredentials` is set to `true`.
- Depending on the value of `builds.android.PROFILE_NAME.credentialsSource`, the credentials are obtained from either the local **credentials.json** file or from the EAS servers. If the `remote` mode is selected but no credentials exist yet, you're prompted to generate a new keystore.
1. Create a tarball containing a copy of the repository. Actual behavior depends on the [VCS workflow](https://expo.fyi/eas-vcs-workflow) you are using.
1. Upload the project tarball to a private AWS S3 bucket and send the build request to EAS Build.
### Remote steps
Next, this is what happens when EAS Build picks up your request:
1. Create a new Docker container for the build.
- Every build gets its own fresh container with all build tools installed there (Java JDK, Android SDK, NDK, and so on).
1. Download the project tarball from a private AWS S3 bucket and unpack it.
1. [Create **.npmrc**](/build-reference/private-npm-packages) if `NPM_TOKEN` is set.
1. Run the `eas-build-pre-install` script from **package.json** if defined.
1. Run `npm install` in the project root (or `yarn install` if `yarn.lock` exists).
1. Run `npx expo-doctor` to diagnose potential issues with your project configuration.
1. Additional step for **managed** projects: Run `npx expo prebuild` to convert the project to a bare one. This step will use the versioned Expo CLI.
1. Restore a previously saved cache identified by the `cache.key` value in the [build profile](/build/eas-json).
1. Run the `eas-build-post-install` script from **package.json** if defined.
1. Restore the keystore (if it was included in the build request).
1. Inject the signing [configuration into **build.gradle**](#configuring-gradle).
1. Run `./gradlew COMMAND` in the **android** directory inside your project.
- `COMMAND` is the command defined in your **eas.json** at `builds.android.PROFILE_NAME.gradleCommand`. It defaults to `:app:bundleRelease` which produces the AAB (Android App Bundle).
1. **Deprecated:** Run the `eas-build-pre-upload-artifacts` script from **package.json** if defined.
1. Store a cache of files and directories defined in the [build profile](/build/eas-json/). Subsequent builds will restore this cache.
1. Upload the application archive to AWS S3.
- The artifact path can be configured in **eas.json** at `builds.android.PROFILE_NAME.applicationArchivePath`. It defaults to `android/app/build/outputs/**/*.{apk,aab}`. We're using the [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) package for pattern matching.
1. If the build was successful: run the `eas-build-on-success` script from **package.json** if defined.
1. If the build failed: run the `eas-build-on-error` script from **package.json** if defined.
1. Run the `eas-build-on-complete` script from **package.json** if defined. The `EAS_BUILD_STATUS` env variable is set to either `finished` or `errored`.
1. Upload the build artifacts archive to a private AWS S3 bucket if `buildArtifactPaths` is specified in the build profile.
## Project auto-configuration
Every time you want to build a new Android app binary, we validate that the project is set up correctly so we can seamlessly run the build process on our servers. This mainly applies to bare projects, but similar steps are run when building managed projects.
### Android keystore
Android requires you to sign your application with a certificate. That certificate is stored in your keystore. The Google Play Store identifies applications based on the certificate. This means that if you lose your keystore, you may not be able to update your application in the store. However, with [Play App Signing](https://developer.android.com/studio/publish/app-signing#app-signing-google-play), you can mitigate the risk of losing your keystore.
Your application's keystore should be kept private. **Under no circumstances should you check it in to your repository.** Debug keystores are the only exception because we don't use them for uploading apps to the Google Play Store.
### Configuring Gradle
Your app binary needs to be signed with a keystore. Since we're building the project on a remote server, we had to come up with a way to provide Gradle with credentials which aren't, for security reasons, checked in to your repository. In one of the remote steps, we inject the signing configuration into your **build.gradle**. EAS Build creates the **android/app/eas-build.gradle** file with the following contents:
```groovy android/app/eas-build.gradle
// Build integration with EAS
import java.nio.file.Paths
android {
signingConfigs {
release {
// This is necessary to avoid needing the user to define a release signing config manually
// If no release config is defined, and this is not present, build for assembleRelease will crash
}
}
buildTypes {
release {
// This is necessary to avoid needing the user to define a release build type manually
}
debug {
// This is necessary to avoid needing the user to define a debug build type manually
}
}
}
tasks.whenTaskAdded {
android.signingConfigs.release {
def credentialsJson = rootProject.file("../credentials.json");
def credentials = new groovy.json.JsonSlurper().parse(credentialsJson)
def keystorePath = Paths.get(credentials.android.keystore.keystorePath);
def storeFilePath = keystorePath.isAbsolute()
? keystorePath
: rootProject.file("..").toPath().resolve(keystorePath);
storeFile storeFilePath.toFile()
storePassword credentials.android.keystore.keystorePassword
keyAlias credentials.android.keystore.keyAlias
if (credentials.android.keystore.containsKey("keyPassword")) {
keyPassword credentials.android.keystore.keyPassword
} else {
// key password is required by Gradle, but PKCS keystores don't have one
// using the keystore password seems to satisfy the requirement
keyPassword credentials.android.keystore.keystorePassword
}
}
android.buildTypes.release {
signingConfig android.signingConfigs.release
}
android.buildTypes.debug {
signingConfig android.signingConfigs.release
}
}
```
The most important part is the `release` signing config. It's configured to read the keystore and passwords from the **credentials.json** file at the project root. Even though you're not required to create this file on your own, it's created and populated with your credentials by EAS Build before running the build.
This file is imported in **android/app/build.gradle** like this:
```groovy android/app/build.gradle
// ...
apply from: "./eas-build.gradle"
```
## iOS build process
Learn how an iOS project is built on EAS Build.
This page describes the process of building iOS projects with EAS Build. You may want to read this if you are interested in the implementation details of the build service.
## Build process
Let's take a closer look at the steps for building iOS projects with EAS Build. We'll first run some steps on your local machine to prepare the project, and then we'll build the project on a remote service.
### Local steps
The first phase happens on your computer. EAS CLI is in charge of completing the following steps:
1. If `cli.requireCommit` is set to `true` in **eas.json**, check if the git index is clean - this means that there aren't any uncommitted changes. If it's not clean, EAS CLI will provide an option to commit local changes for you or abort the build process.
1. Prepare the credentials needed for the build.
- Depending on the value of `builds.ios.PROFILE_NAME.credentialsSource`, the credentials are obtained from either the local **credentials.json** file or from the EAS servers. If the `remote` mode is selected but no credentials exist yet, you're offered to generate them.
1. **Bare** projects require an additional step: check whether the Xcode project is configured to be buildable on the EAS servers (to ensure the correct bundle identifier and Apple Team ID are set).
1. Create the tarball containing a copy of the repository. Actual behavior depends on the [VCS workflow](https://expo.fyi/eas-vcs-workflow) you are using.
1. Upload the project tarball to a private AWS S3 bucket and send the build request to EAS Build.
### Remote steps
In this next phase, this is what happens when EAS Build picks up your request:
1. Create a new macOS VM for the build.
- Every build gets its own fresh macOS VM with all build tools installed there (Xcode, Fastlane, and so on).
1. Download the project tarball from a private AWS S3 bucket and unpack it.
1. [Create **.npmrc**](/build-reference/private-npm-packages) if `NPM_TOKEN` is set.
1. Run the `eas-build-pre-install` script from **package.json** if defined.
1. Run `npm install` in the project root (or `yarn install` if **yarn.lock** exists).
1. Run `npx expo-doctor` to diagnose potential issues with your project configuration.
1. Restore the credentials
- Create a new keychain.
- Import the Distribution Certificate into the keychain.
- Write the Provisioning Profile to the **~/Library/MobileDevice/Provisioning Profiles** directory.
- Verify that the Distribution Certificate and Provisioning Profile match (every Provisioning Profile is assigned to a particular Distribution Certificate and cannot be used for building the iOS with any other certificate).
1. Additional step for **managed** projects: Run `npx expo prebuild` to convert the project to a bare one. This step will use the versioned Expo CLI.
1. Restore a previously saved cache identified by the `cache.key` value in the [build profile](/build/eas-json).
1. Run `pod install` in the **ios** directory inside your project.
1. Run the `eas-build-post-install` script from **package.json** if defined.
1. Update the Xcode project with the ID of the Provisioning Profile.
1. Create **Gymfile** in the **ios** directory if it does **not** already exist (check out the [Default Gymfile](#default-gymfile) section).
1. Run `fastlane gym` in the **ios** directory.
1. **Deprecated:** Run the `eas-build-pre-upload-artifacts` script from **package.json** if defined.
1. Store a cache of files and directories defined in the [build profile](/build/eas-json). **Podfile.lock** is cached by default. Subsequent builds will restore this cache.
1. Upload the application archive to a private AWS S3 bucket.
- The artifact path can be configured in **eas.json** at `builds.ios.PROFILE_NAME.applicationArchivePath`. It defaults to **ios/build/App.ipa**. You can specify a glob-like pattern for `applicationArchivePath`. We're using the [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) package under the hood.
1. If the build was successful: run the `eas-build-on-success` script from **package.json** if defined.
1. If the build failed: run the `eas-build-on-error` script from **package.json** if defined.
1. Run the `eas-build-on-complete` script from **package.json** if defined. The `EAS_BUILD_STATUS` env variable is set to either `finished` or `errored`.
1. Upload the build artifacts archive to a private AWS S3 bucket if `buildArtifactPaths` is specified in the build profile.
## Building iOS projects with Fastlane
We're using [Fastlane](https://fastlane.tools/) for building iOS projects. To be more precise, we're using the `fastlane gym` command ([see the Fastlane docs to learn more](https://docs.fastlane.tools/actions/gym/)). This command allows you to declare the build configuration in **Gymfile**.
EAS Build can use your own **Gymfile**. All you need to do is to place this file in the **ios** directory.
### Default Gymfile
If the **ios/Gymfile** file doesn't exist, the iOS builder creates a default one which looks similar to the following:
```rb ios/Gymfile
suppress_xcode_output(true)
clean(true)
scheme("app")
export_options({
method: "app-store",
provisioningProfiles: {
"com.expo.eas.builds.test.application" => "dd83ed9c-4f89-462e-b901-60ae7fe6d737"
}
})
export_xcargs "OTHER_CODE_SIGN_FLAGS=\"--keychain /tmp/path/to/keychain\""
disable_xcpretty(true)
output_directory("./build")
output_name("App")
```
## Build configuration process
Learn how EAS CLI configures a project for EAS Build.
In this guide, you will learn what happens when EAS CLI configures your project with `eas build:configure` (or `eas build`, which runs this same process if the project is not yet configured).
EAS CLI performs the following steps when configuring your project:
Step 1:
## Ask you about the platform(s) to configure
When you run the command for the first time, it will initialize your EAS project and ask you to select the platform(s) you want to configure. If you only want to use EAS Build for a single platform, that's fine. If you change your mind, you can come back and configure the other later.
Step 2:
## Create eas.json
The command will create an **eas.json** file in the root directory with the default configuration. It looks something like this:
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {}
}
}
```
If you have a bare project, it will look a bit different.
This is your EAS Build configuration. It defines three build profiles named `"development"`, `"preview"`, and `"production"` (you can have multiple build profiles like `"production"`, `"debug"`, `"testing"`, and so on) for each platform. If you want to learn more about **eas.json** see the [Configuration with **eas.json**](/build/eas-json) page.
Step 3:
## Configure the project
This step varies depending on the project type you have.
Step 3.1:
### Initialization complete
This completes the initialization of your project to be compatible with EAS Build.
Step 3.2:
### Expo project
If you haven't configured your **app.json** with `android.package` and/or `ios.bundleIdentifier` yet, EAS CLI will prompt you to specify them when you create your first build.
- `android.package` will be used as the Android application ID which is used to identify your app on the Google Play Store
- `ios.bundleIdentifier` will be used to identify you app on the Apple App Store
In the example above, the `eas build --platform android` command prompts to set the Android application ID. If you run the command with `--platform ios`, it will prompt you to set the iOS bundle identifier.
Step 3.3:
### Bare React Native project
There are no additional steps for bare projects.
Step 4:
## Next steps
That's all there is to configuring a project to be compatible with EAS Build. There is one more step, if you set `cli.requireCommit` to `true` in your **eas.json** — you'll be prompted to commit all the changes we made for you. You can choose to review them before committing, and you can either specify the git commit message or use a default message.
## Build server infrastructure
Learn about the current build server infrastructure when using EAS.
## Builder IP addresses
A list of the IP addresses of the build servers is available [in this file](https://expo.dev/eas-build-worker-ips.txt). We do not expect to change the list often. The list includes "Last-Modified" and "Expires" ISO 8601 timestamps that respectively specify the last time the list was updated and the time until which we commit to not change the list.
Linux runners are hosted in Google Cloud Platform. macOS runners are hosted in our own macOS cloud.
## Configuring build environment
Images for each platform have one specific version of Node.js, Yarn, CocoaPods, Xcode, Ruby, Fastlane, and so on. You can override some of the versions in [eas.json](/build/eas-json). If there is no dedicated configuration option you are looking for, you can use [npm hooks](/build-reference/npm-hooks) to install or update any system dependencies with `apt-get` or `brew`. Consider that those customizations are applied during the build and will increase your build times.
When selecting an image for the build you can use the full name provided below or one of the aliases: `auto`, `latest`, or for a particular SDK such as `sdk-52`.
- The use of a specific name guarantees a consistent environment with only minor updates.
- When using the `auto` alias, the build image will be selected based on the project configuration, Expo SDK version, and React Native version. You can check what image is used for a build in the **Spin up build environment** build logs section.
- The `latest` alias will be assigned to the image with the most up-to-date versions of the software.
- The `sdk-52` alias will be assigned to the image best suited for SDK 52 builds.
- The `sdk-51` alias will be assigned to the image best suited for SDK 51 builds.
- The `sdk-50` alias will be assigned to the image best suited for SDK 50 builds.
- SDK aliases will be updated with every new SDK release.
- The `latest` alias will be updated with every new image release.
> **info** **Note:** If you do not provide `image` in **eas.json**, your build by default will use the `auto` alias.
## Android build server configurations
Android builders run on virtual machines in an isolated environment. Every build gets its own dedicated VM instance.
- Build resources:
- [npm cache deployed with Kubernetes](/build-reference/caching/#javascript-dependencies)
- [Maven cache deployed with Kubernetes](/build-reference/caching/#android-dependencies)
- Global Gradle configuration in **~/.gradle/gradle.properties**:
```ini ~/.gradle/gradle.properties
org.gradle.jvmargs=-Xmx14g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.parallel=true
org.gradle.configureondemand=true
org.gradle.daemon=false
```
- Global npm configuration in **~/.npmrc**:
```ini ~/.npmrc
registry=http://10.4.0.19:4873
```
- Global Yarn configuration in **~/.yarnrc.yml**:
```yml ~/.yarnrc.yml
unsafeHttpWhitelist:
- '*'
npmRegistryServer: 'http://10.4.0.19:4873'
enableImmutableInstalls: false
```
### Android server images
#### `ubuntu-22.04-jdk-17-ndk-r26b` (`latest`, `sdk-51`, `sdk-52`)
Note: Details
---
- Docker image: `ubuntu:jammy-v20240614`
- NDK 26.1.10909125
- Node.js 18.18.0
- Bun 1.1.13
- Yarn 1.22.21
- pnpm 9.3.0
- npm 9.8.1
- Java 17
- node-gyp 10.1.0
---
#### `ubuntu-22.04-jdk-17-ndk-r25b` (`sdk-50`)
Note: Details
---
- Docker image: `ubuntu:jammy-20220810`
- NDK 25.1.8937393
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.9.2
- npm 9.8.1
- Java 17
- node-gyp 10.0.1
---
#### `ubuntu-22.04-jdk-11-ndk-r23b` (`sdk-49`)
Note: Details
---
- Docker image: `ubuntu:jammy-20220810`
- NDK 23.1.7779620
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.7.5
- npm 9.8.1
- Java 11
- node-gyp 10.0.1
---
#### `ubuntu-22.04-jdk-17-ndk-r21e`
Note: Details
---
- Docker image: `ubuntu:jammy-20220810`
- NDK 21.4.7075529
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.9.2
- npm 9.8.1
- Java 17
- node-gyp 10.0.1
---
#### `ubuntu-22.04-jdk-11-ndk-r21e`
Note: Details
---
- Docker image: `ubuntu:jammy-20220810`
- NDK 21.4.7075529
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.7.5
- npm 9.8.1
- Java 11
- node-gyp 10.0.1
---
#### `ubuntu-22.04-jdk-8-ndk-r21e` (deprecated)
Note: Details
---
- Docker image: `ubuntu:jammy-20220810`
- NDK 21.4.7075529
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 8
- node-gyp 10.0.1
---
#### `ubuntu-20.04-jdk-11-ndk-r23b` (deprecated)
Note: Details
---
- Docker image: `ubuntu:focal-20220823`
- NDK 23.1.7779620
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 11
- node-gyp 10.0.1
---
#### `ubuntu-20.04-jdk-11-ndk-r21e` (deprecated)
Note: Details
---
- Docker image: `ubuntu:focal-20220823`
- NDK 21.4.7075529
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 11
- node-gyp 10.0.1
---
#### `ubuntu-20.04-jdk-8-ndk-r21e` (deprecated)
Note: Details
---
- Docker image: `ubuntu:focal-20220823`
- NDK 21.4.7075529
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 8
- node-gyp 10.0.1
---
#### `ubuntu-20.04-jdk-11-ndk-r19c` (deprecated)
Note: Details
---
- Docker image: `ubuntu:focal-20220823`
- NDK 19.2.5345600
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 11
- node-gyp 10.0.1
---
#### `ubuntu-20.04-jdk-8-ndk-r19c` (deprecated)
Note: Details
---
- Docker image: `ubuntu:focal-20220823`
- NDK 19.2.5345600
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 7.0.0
- npm 9.8.1
- Java 8
- node-gyp 10.0.1
---
## iOS build server configurations
iOS builder VMs run on Mac mini hosts in an isolated environment. Every build gets its own fresh macOS VM. For more information, see [iOS-specific resource classes](/eas/json/#resourceclass-2).
- Hardware:
- M1 3.2GHz 8-Core, 16 GB RAM
- M2 3.9GHz 8-Core, 24 GB RAM
- M2 Pro 3.9GHz 12-Core, 32 GB RAM
- Build resources:
- [npm cache](/build-reference/caching/#javascript-dependencies)
- [CocoaPods cache](/build-reference/caching/#ios-dependencies)
- [`cocoapods-nexus-plugin`](https://github.com/expo/eas-build/tree/main/packages/cocoapods-nexus-plugin)
- Global npm configuration in **~/.npmrc**:
```ini ~/.npmrc
registry=http://10.94.183.70:4873
```
- Global Yarn configuration in **~/.yarnrc.yml**:
```yml ~/.yarnrc.yml
unsafeHttpWhitelist:
- '*'
npmRegistryServer: 'http://10.94.183.70:4873'
enableImmutableInstalls: false
```
### iOS server images
#### `macos-sonoma-14.6-xcode-16.1` (`latest`, `sdk-52`)
Note: Details
---
- macOS Sonoma 14.6
- Xcode 16.1 (16B40)
- Node.js 18.18.0
- Bun 1.1.33
- Yarn 1.22.21
- pnpm 9.12.3
- npm 9.8.1
- fastlane 2.225.0
- CocoaPods 1.16.2
- Ruby 3.2
- node-gyp 10.2.0
---
#### `macos-sonoma-14.6-xcode-16.0`
Note: Details
---
- macOS Sonoma 14.6
- Xcode 16.0 (16A242d)
- Node.js 18.18.0
- Bun 1.1.27
- Yarn 1.22.21
- pnpm 9.10.0
- npm 9.8.1
- fastlane 2.222.0
- CocoaPods 1.15.2
- Ruby 3.2
- node-gyp 10.2.0
---
#### `macos-sonoma-14.5-xcode-15.4` (`sdk-51`, `sdk-50`, `sdk-49`)
Note: Details
---
- macOS Sonoma 14.5
- Xcode 15.4 (15F31d)
- Node.js 18.18.0
- Bun 1.1.13
- Yarn 1.22.21
- pnpm 9.3.0
- npm 9.8.1
- fastlane 2.220.0
- CocoaPods 1.14.3
- Ruby 2.7
- node-gyp 10.1.0
---
#### `macos-sonoma-14.4-xcode-15.3`
Note: Details
---
- macOS Sonoma 14.4.1
- Xcode 15.3 (15E204a)
- Node.js 18.18.0
- Bun 1.0.35
- Yarn 1.22.21
- pnpm 8.14.1
- npm 9.8.1
- fastlane 2.219.0
- CocoaPods 1.14.3
- Ruby 2.7
- node-gyp 10.0.1
---
#### `macos-ventura-13.6-xcode-15.2`
Note: Details
---
- macOS Ventura 13.6
- Xcode 15.2 (15C500b)
- Node.js 18.18.0
- Bun 1.0.23
- Yarn 1.22.21
- pnpm 8.14.1
- npm 9.8.1
- fastlane 2.219.0
- CocoaPods 1.14.3
- Ruby 2.7
- node-gyp 10.0.1
---
#### `macos-ventura-13.6-xcode-15.1`
Note: Details
---
- macOS Ventura 13.6
- Xcode 15.1 (15C65)
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.12.1
- npm 9.8.1
- fastlane 2.217.0
- CocoaPods 1.14.3
- Ruby 2.7
- node-gyp 10.0.1
---
#### `macos-ventura-13.6-xcode-15.0`
Note: Details
---
- macOS Ventura 13.6
- Xcode 15.0 (15A240d)
- Node.js 18.18.0
- Bun 1.0.14
- Yarn 1.22.19
- pnpm 8.7.6
- npm 9.8.1
- fastlane 2.216.0
- CocoaPods 1.13.0
- Ruby 2.7
- node-gyp 10.0.1
---
### Supported Xcode versions
We aim to support all stable Xcode releases that allow you to submit your app to the App Store Connect when used during the build process.
This usually means that we support the latest stable Xcode version and the previous one (until the new [minimal Xcode version requirement](https://developer.apple.com/news/upcoming-requirements/?id=04292024a) is introduced by Apple).
## iOS App Extensions
Learn how to use app extensions with EAS Build to add custom functionality.
App extensions let you extend custom functionality and content beyond your app and make it available to users while they're interacting with other apps or iOS system functionality. EAS Build provides affordances for including app extensions in both bare and managed projects.
## Managed projects (experimental support)
A typical, simple managed project we have a single application target and no app extensions. You can add an app extension to your project by writing a [config plugin](/config-plugins/introduction/) (or using a library that creates an extension with its own config plugin). Config plugins let you add targets to the Xcode project that is generated during the "Prebuild" phase of a build job.
Declaring app extensions with `extra.eas.build.experimental.ios.appExtensions` in your app config makes it possible for EAS CLI to know what app extensions exist _before the build starts_ (before the Xcode project has been generated) to ensure that the required credentials are generated and validated. Config plugins are also able to modify the app config, and in most cases, if you are using a library that adds an extension then the config plugin will also add the required configuration to declare the extension in your app config. If you are writing a library, we recommend that you consider this. The following is an example of what this would look like if it were declared directly in **app.json**:
```json app.json
{
"expo": {
...
"extra": {
"eas": {
"build": {
"experimental": {
"ios": {
"appExtensions": [
{
"targetName": "myappextension",
"bundleIdentifier": "com.myapp.extension",
"entitlements": {
"com.apple.example": "entitlement value"
}
}
}
]
}
}
}
}
}
```
## Bare projects
When you build a bare project, EAS CLI will automatically detect app extensions configured in your Xcode project and generate all necessary credentials for each target, or you can provide them in **credentials.json** For more information, see [Multi target project](/app-signing/local-credentials/#multi-target-project).
## Run E2E tests on EAS Build
Learn how to set up and run E2E tests on EAS Build with Maestro.
> **info** This guide will evolve over time as support for E2E testing in EAS Build improves. If you are looking for an archived guide on how to run E2E tests on EAS Build using Detox, you can see it in the [archive section](/archive/e2e-tests/).
In this guide, you will learn how to create and run E2E tests on EAS Build using [Maestro](https://maestro.mobile.dev/), which is one of the most popular tools for running E2E tests in mobile apps.
The example demonstrates how to configure your EAS Build Maestro E2E tests workflow using the [default Expo template](/more/create-expo/#--template). For your own app, you will need to adjust the flows to match your app's UI.
Video Tutorial: [Watch: How to run E2E tests on EAS Build](https://www.youtube.com/watch?v=-o-bfIRrg9U)
Step 1:
## Initialize a new project
You can skip this step if you already have an existing Expo project.
Create a new project using the following commands:
```sh
$ npx create-expo-app@latest eas-tests-example
$ cd eas-tests-example
```
Step 2:
## Configure EAS Build
You can skip this step if you already have EAS Build configured for your project.
The following command creates a new project on Expo servers for your app and creates [**eas.json**](/build/eas-json/) in the project's root directory:
```sh
$ eas init
$ eas build:configure
```
Step 3:
## Disable New Android Builds Infrastructure
Go to [**Project settings**](https://expo.dev/accounts/[account]/projects/[project]/settings) and disable **New Android Builds Infrastructure**.
Unfortunately, the new build infrastructure is incompatible with E2E tests due to the lack of virtualization support required to start Android Emulator. We are working on better solutions for running various tests on EAS.
Step 4:
## Add example Maestro test cases
This is what the UI of the app created from the default Expo template looks like:
Let's create two simple Maestro flows for the example app. Start by creating a directory called **maestro** in the root of your project directory. This directory will contain the flows that you will configure and should be at the same level as **eas.json**.
Inside, create a new file called **home.yml**. This flow will launch the app and assert that the text "Welcome!" is visible on the home screen.
```yaml maestro/home.yml
appId: dev.expo.eastestsexample # This is an example app id. Replace it with your app id.
---
- launchApp
- assertVisible: 'Welcome!'
```
Next, create a new flow called **expand_test.yml**. This flow will open the "Explore" screen in the example app, click on the "File-based routing" collapsible, and assert that the text "This app has two screens" is visible on the screen.
```yaml maestro/expand_test.yml
appId: dev.expo.eastestsexample # This is an example app id. Replace it with your app id.
---
- launchApp
- tapOn: 'Explore.*'
- tapOn: '.*File-based routing'
- assertVisible: 'This app has two screens.*'
```
Step 5:
## Run Maestro tests locally
To run Maestro tests locally, install the Maestro CLI by following the instructions in [Installing Maestro](https://maestro.mobile.dev/getting-started/installing-maestro).
[Install your app on a local Android Emulator or iOS Simulator](/more/expo-cli/#compiling). Open a terminal, navigate to the Maestro directory, and run the following commands to start the tests with the Maestro CLI:
```sh
$ maestro test maestro/expand_test.yml
$ maestro test maestro/home.yml
```
The video below shows a successful run of the **maestro/expand_test.yml** flow:
Step 6:
## Create a custom build workflow for running Maestro E2E tests
The easiest way to run Maestro E2E tests on EAS Build is to create a [custom build workflow](/custom-builds/get-started/). This workflow will build your app and run the Maestro tests on it.
Start by adding a custom build config file to your project. Create a directory **.eas/build** at the same level as **eas.json** in the project. The path and the name of both directories are important for EAS Build to identify that a project contains a custom build config.
Inside, create a new config file called **build-and-maestro-test.yml**. This file defines the custom build workflow config that you want to run. Workflow contains steps that are executed during the custom build process. This custom build config will execute the [`eas/build`](/custom-builds/schema/#easbuild) custom function group to create a build and then the [`eas/maestro_test`](/custom-builds/schema/#easmaestro_test) which is an all-in-one custom function group that installs Maestro, prepares a testing environment (Android Emulator or iOS Simulator) and tests the app using flows specified by the `flow_path` input.
```yaml .eas/build/build-and-maestro-test.yml
build:
name: Create a build and run Maestro tests on it
steps:
- eas/build
- eas/maestro_test:
inputs:
flow_path: |
maestro/home.yml
maestro/expand_test.yml
```
Now modify the **eas.json** by adding a new [build profile](/build/eas-json/#build-profiles) called `build-and-maestro-test`. It will be used to run the custom build config from the **build-and-maestro-test.yml** file. This configuration will build the emulator/simulator version of your app and run the Maestro tests on it.
> **Warning** We have observed that Maestro tests often time out if run on images with Xcode 15.0 or 15.2. Use the [`latest` image](/build-reference/infrastructure/#configuring-build-environment) to avoid any issues.
```json eas.json
{
"build": {
"build-and-maestro-test": {
"withoutCredentials": true,
"config": "build-and-maestro-test.yml",
"android": {
"buildType": "apk",
"image": "latest"
},
"ios": {
"simulator": true,
"image": "latest"
}
}
}
}
```
Step 7:
## Build your app and run E2E tests on EAS Build
To execute a custom build using the `build-and-maestro-test` profile that will build your app and run the Maestro E2E tests afterward, run the following command:
```sh
$ eas build --profile build-and-maestro-test
```
When the flow fails, any Maestro artifacts are automatically uploaded as build artifacts. This includes screenshots saved at `~/.maestro/tests` (the default destination). You can download them from the build page.
## More
If you want to build more advanced custom builds workflows, see the [custom build schema reference](/custom-builds/schema/) for more information.
To learn more about Maestro flows and how to write them, see the [Maestro documentation](https://maestro.mobile.dev).
## Troubleshooting
If you encounter the following error message when starting an Android Emulator for E2E tests on EAS Build:
```text
Failed to configure emulator: emulator with required ID not found.
```
This is likely because the New Build Infrastructure is enabled for your project. Go to [**Project Settings**](https://expo.dev/accounts/[account]/projects/[project]/settings) and disable **New Android Builds Infrastructure**.
## Ignore files via .easignore
Learn how to configure EAS to ignore unnecessary files during the build process.
A **.easignore** file defines which files [EAS](https://expo.dev/eas) should ignore when uploading your project to the [EAS Build](/build/introduction) servers.
> **info** Ignoring unnecessary files can help reduce your app's archive size and upload time.
By default, the [EAS CLI](/build/setup/#install-the-latest-eas-cli) refers to the [**.gitignore**](https://git-scm.com/docs/gitignore) file (if it exists) to determine which files to ignore. If you create a **.easignore** file, the EAS CLI prioritizes it over the **.gitignore** file. When creating a **.easignore** file, include all files and directories from your **.gitignore** file and add additional files you want to ignore.
Step 1:
Create a **.easignore** file in the root of your project.
Step 2:
Copy the content of the **.gitignore** file into the **.easignore** file. Then, add any files that are unnecessary for the build process.
```bash .easignore
# Copy everything from your .gitignore file here
# Ignore files and directories that EAS Build doesn't need to build your app
/docs
# Ignore native directories (if you are using EAS Build)
/android
/ios
# Ignore test coverage reports
/coverage
```
If your project does not contain **android** and **ios** directories, [EAS Build will run Prebuild](/workflow/prebuild/#usage-with-eas-build) to generate these native directories before compilation.
Step 3:
Save the file and trigger a new build.
```sh
$ eas build --platform ios --profile development
```
You've successfully configured your **.easignore** file.
## Adding files to your project upload with .easignore
In addition to ignoring additional files beyond what is in your gitignore file, you can also use the **.easignore** file to include files with your EAS Build upload that are not committed to source control. This is useful if you have custom scripts that generate temporary files needed for your build process just prior to build. To upload a file not in source control to EAS Build, add it to the **.easignore** file with a `!` prefix, along with the rest of your .gitignore contents. The `!` prefixed file should be last, so it takes precedence over any prior rules that would ignore it.
```bash .easignore
# Copy everything from your .gitignore file here
/android
/ios
# Include a file not in source control
!temp_file.json
```
## EAS Build Limitations
Learn about the current limitations of EAS Build.
EAS Build is designed to work for any React Native project. However, it is good to be aware of certain limitations that we plan to address since they could prevent you from being able to use the service for your applications or might cause an inconvenience.
## Fixed memory and CPU limits on build worker servers
The resources available might be insufficient to build your app if your build process requires a significant amount of memory. In this case, consider using a [`large` resource class](/eas/json/#resourceclass) in the **eas.json**. See [Android-specific resource class](/build-reference/infrastructure/#android-build-server-configurations) and [iOS-specific resource class](/build-reference/infrastructure/#ios-build-server-configurations).
See [Server infrastructure reference](/build-reference/infrastructure) for more information. It contains the most up-to-date information about the current specifications of the Android (Ubuntu) and iOS (macOS) build servers.
## Limited dependency caching
Build jobs for Android install npm and Maven dependencies from a local cache. Build jobs for iOS install npm dependencies from a local cache, and CocoaPods artifacts from a cache server.
Intermediate artifacts like **node_modules** directories are not cached and restored (for example, based on **package-lock.json** or **yarn.lock**), but if you commit them to your Git repository then they will be uploaded to build servers.
See [dependency caching](/build-reference/caching) for more information.
## Maximum build duration of 2 hours
If your build takes longer than 2 hours to run, it will be canceled. This limit is lower on the free plan, and the limit is subject to change in the future.
## Maximum number of pending builds is 50 per platform per account
If you have more than 50 builds pending for a platform, new builds will be rejected until the number of pending builds drops below the limit.
## Yarn workspaces is recommended for monorepos
> **Note**: Official guidance for package managers other than Yarn is limited.
While you likely can have success using other monorepo tools like Nx if you are willing to dig in and understand the tooling and get your hands dirty, the Expo team will be unable to provide support and guidance on those tools. We recommend using [Yarn Workspaces](https://yarnpkg.com/en/docs/workspaces) because it is the only monorepo tool that we provide first-class integration with at the moment.
## Get notified about changes
To be notified as progress is made on these items, you can subscribe to the EAS newsletter on [expo.dev/eas](https://expo.dev/eas).
# EAS Hosting
## EAS Hosting
EAS Hosting is a service for quickly deploying the web projects built using the Expo Router library and React Native web.
> **info** EAS Hosting is currently in preview. If you have any issues or feedback, post to our [Discord](https://chat.expo.dev/) channel or send us an email at hosting@expo.dev.
**EAS Hosting** is a service for quickly deploying your web projects built using [Expo Router](/router/introduction/) and React Native web. It seamlessly integrates with the Expo CLI, allowing you to automate the deployment of API routes, server functions, and server-side assets.
Hosting supports both [static](/router/reference/static-rendering/) and [server mode](/router/reference/api-routes/) web projects and offers the fastest path from `npx create-expo-app` to a fully deployed web app with API routes.
### Get started
## Deploy your first Expo Router and React app
Learn how to deploy your Expo Router and React apps to EAS Hosting.
EAS Hosting is a react hosting service that allows you to deploy an exported Expo web build to a preview or production URL.
This guide will walk you through the process of creating your first web deployment.
Video Tutorial: [Watch: Deploy your Expo Router web project](https://www.youtube.com/watch?v=NaKsfWciJLo)
## Why EAS Hosting
Historically, traditional website hosting services were recommended for deploying Expo Router and React apps. However, this approach doesn't address the unique challenges of dealing with native apps. Here are some key limitations:
- Version synchronization: During the app store publishing process, you may need to deploy new versions of your servers.
- Request routing complexity: Different versions of your native app may require routing to specific server versions. This can create additional complexity when handling requests.
- Platform-specific analysis: When running native apps, you need enhanced observability for platform-specific metrics.
The introduction of EAS Hosting aims to address these limitations.
## Prerequisites
Note: An Expo user account
---
EAS Hosting is available to anyone with an Expo account, regardless of whether you pay for EAS or use the Free plan. You can sign up at [expo.dev/signup](https://expo.dev/signup).
Paid subscribers can create more deployments, have more bandwidth, storage, requests, and may set up a custom domain. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing#host).
---
Note: An Expo Router web project
---
If using an existing project, ensure that in your project's app config, the web output ([`expo.web.output`](/versions/latest/config/app/#output)) is set to either `static` or `server` (single page apps are not supported on EAS Hosting).
Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide.
Run the following command to create a new project:
```sh
$ npx create-expo-app@latest my-app
```
---
Step 1:
## Install the latest EAS CLI
EAS CLI is the command line app you will use to interact with EAS services from your terminal. To install it, run the command:
```sh
$ npm install --global eas-cli
```
You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version.
> We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation.
Step 2:
## Log in to your Expo account
If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in:
```sh
$ eas login
```
You can check whether you are logged in by running `eas whoami`.
Step 3:
## Prepare your project
In your app config file set [`expo.web.output`](/versions/latest/config/app/#output) to either `static` or `server`.
- `static`: Exports your Expo app to a [statically generated web app](/router/reference/static-rendering/)
- `server`: Supports [server functions](/guides/server-components/#react-server-functions) and [API routes](/router/reference/api-routes/) as well as the static pages for your website
> Don't worry if you're not sure which output mode you need, you can always change this value later and re-deploy.
Step 4:
### Export your app
You need to export your web project into a **dist** directory. To do this, run:
```sh
$ npx expo export --platform web
```
> Remember to re-run this command every time before deploying.
Step 5:
### Deploy your app
Now publish your website to EAS Hosting:
```sh
$ eas deploy
```
The first time you run this command, it will:
1. Prompt you to connect an EAS project if you haven't done so yet
2. Ask you to choose a preview subdomain name
> **info** A **preview subdomain name** is a prefix used for the preview URL of your app.
> For example, if you choose `my-app` as your preview subdomain name, your preview URL would look something like this: `https://my-app--or1170q9ix.expo.app/`, and your production URL would be: `https://my-app.expo.app/`.
Once your deployment is complete, the CLI will output a preview URL for where your deployed app is accessible, as well as a link to the deployment details on the EAS Dashboard.
## Assign aliases and promote to production
Learn about deployment URLs and how to set up aliases.
## Deployments
Deployments to EAS Hosting are immutable. Each deployment is accessible via a unique deployment URL consisting of the preview subdomain name and the deployment ID.
### Preview subdomain name
To activate EAS Hosting for a project, you'll need to choose a **preview subdomain name**. You can do this via the **Hosting** section of your project on the [expo.dev](http://expo.dev) website. Alternatively, you'll be prompted to choose a preview subdomain when you create your first deployment using the EAS CLI.
### Preview and production URLs
A **preview subdomain name** is a prefix used for the preview URL of your app. For example, if you choose `my-app` as the preview subdomain name, your preview URL would be: `https://my-app--or1170q9ix.expo.app/`, and your production URL would be: `https://my-app.expo.app/`.
### Deployment ID
Each deployment is identifiable using a unique deployment ID. This ID can be customized but will be a random string of letters and numbers by default.
Deployments are immutable. Once they are deployed, they cannot be changed and will always remain accessible and identifiable using their deployment ID.
## Aliases
Aliases are user-defined values used for creating custom URLs for deployments.
To make a deployment and assign it to an alias, use the `--alias` option:
```sh
$ eas deploy --alias hello
```
The above command will create a deployment with both a standard URL at `https://my-app--or1170q9ix.expo.app/` and an alias at `https://my-app--hello.expo.app/`.
> Aliases are unique per project. If you choose an alias that was already in use, it will get re-assigned to the new deployment.
A single deployment can have multiple aliases. Aliases can also be assigned to an existing deployment by using the `--id` option:
```sh
$ eas deploy:alias --id=my-id
```
In the above command, the `my-id` is the ID in the preview URL.
Aliases can have arbitrary names. For example, if you want to create a staging environment, you may create an alias called `staging` and assign a deployment to it.
### Production alias
If your preview subdomain name is `my-app`, your production URL will be `https://my-app.expo.app/`.
Similar to other aliases, a deployment can be promoted to production using `--prod` option:
```sh
$ eas deploy --prod
```
Existing deployment can also be promoted to production using its deployment ID with the `--id` option:
```sh
$ eas deploy:alias --prod --id=deploymentId
```
## Terminology
In the following example, `my-app` is selected as the preview subdomain name:
- `https://my-app--or1170q9ix.expo.app/` : Preview URL, which is unique and where your deployment is available.
- `my-app`: Preview subdomain name. Globally unique prefix tied to your project.
- `or1170q9ix`: Deployment ID, which is unique to this deployment.
- `https://my-app--hello.expo.app/`: A deployment URL with an alias.
- `hello`: User-defined alias.
- `https://my-app.expo.app/`: Production deployment URL.
## Environment variables
Learn how to use environment variables in your project.
An Expo Router web project may include client-side and server-side environment variables. The client-side environment variables are embedded in the app and inlined in the JavaScript bundle when you run `npx expo export`. The server-side environment variables are kept securely on the server and are deployed with the API routes code when you run `eas deploy`.
> **warning** With EAS environment variables, only **plain text** and **sensitive** [environment variables](/eas/environment-variables/#visibility) can be used. Secrets cannot be deployed with EAS Hosting.
## Client-side environment variables
All the code that runs in the browser is client-side. In an Expo Router project, this includes all code that is _not_ an API Route or server function.
The environment variables in your client-side code are inlined at build time. You should never put any sensitive information in your client-side code, which is why all client-side environment variables must be prefixed with [`EXPO_PUBLIC_`](/guides/environment-variables/).
Then, when you run `npx expo export`, all instances of `process.env.EXPO_PUBLIC_*` environment variables will be replaced with values from the environment.
## Server-side environment variables
All the code in your API routes (that is, files that end with **+api.ts**) runs on the server.
Since the code running on the server is never visible to the app user, the server-side code can safely use sensitive environment variables such as API keys and tokens.
Unlike client-side environment variables, server-side environment variables are not inlined in the code, they are uploaded with the deployment when you run the `eas deploy` command.
## Environment variables for local development
For local development, both server- and client-side environment variables are loaded from a [local **.env** file](/guides/environment-variables/), which should be gitignored. If you're using EAS environment variables, use [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) to retrieve the environment variables for `development`, `preview`, or `production`.
## Storing environment variables
The two primary ways of handling environment variables are via EAS environment variables or **.env** files.
### Using EAS environment variables
With [EAS environment variables](/eas/environment-variables/), you store all variables in EAS using the EAS CLI or the web UI and pull them down locally as and when necessary.
When using the EAS environment variables, use the [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) command to pull down the environment you want to develop against.
For deploying a project with environment variables, note that the environment variables for the client- and server-side code are included at different steps:
- Running `npx expo export --platform web` will inline the `EXPO_PUBLIC_` variables in the frontend code. So ensure that your **.env** or **.env.local** file includes the correct environment variables before running the export command.
- `eas deploy --environment production` will include all variables for the given environment (in this case, `production`) in the API routes. EAS Environment variables loaded with the `--environment` flag will take precedence over ones defined in **.env** and **.env.local** files.
> **warning** **Environment variables are per deployment, and deployments are immutable**. This means that after changing an environment variable, you will need to re-export your project, and re-deploy in order for them to be updated.
## Custom domain
Set up a custom domain for your production deployment.
By default, your production deployment on EAS Hosting will look like this: `my-app.expo.app` , where `my-app` is your chosen preview subdomain name. If you own a domain, you may assign it as a custom domain to the production deployment.
Each project can have exactly one custom domain, which is assigned to the production deployment.
> **info** **Note**: Setting up a custom domain is a premium feature and isn't available on the free plan. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing).
## Prerequisites
Note: An EAS Hosting project with a production deployment
---
The custom domain will always load the production deployment. Therefore, to add a custom domain to your project, you will need a deployment that's been promoted to production first.
---
Note: A domain name
---
You will need to own a domain name you want to use.
---
## Assigning a custom domain
1. In your project's dashboard, navigate to [Hosting settings](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/settings).
2. If you do not have a production deployment, you'll be prompted to assign one first.
3. Under **Custom domain**, enter the custom domain you'd like to set up. Both apex domains and subdomains are supported. If you own `example.com`, you can select:
- `example.com`: apex domain
- `anything.example.com`: a subdomain
4. Next, you'll be prompted to fill out some DNS records with your DNS provider:
- **Verification**: to prove you own the domain
- **SSL**: to set up SSL certificates
- **CNAME** (subdomains) or **A record** (apex domains): to point the domain at your production deployment
5. Press the refresh button until all checks pass. Depending on your DNS provider, this step usually only takes a couple of minutes.
> If you require for the domain name switchover to be **zero downtime**, it's important to fill out these records one by one in the order they are presented in the table.
> That is, add the **TXT** record for verification, press the refresh button until the UI says verification is successful, then proceed to the next one.
> If downtime isn't important or relevant, you may add all three DNS records at once.
After assigning a custom domain to your app, the custom domain will route to your **production** deployment.
## API Routes
Learn how to inspect requests from API routes on the EAS Hosting dashboard.
> **info** This page is for EAS Hosting specific details about API routes. For general documentation about the topic, see the [API routes](/router/reference/api-routes/) documentation under Expo Router.
Crashes, logs, and requests that occur in API routes can be inspected on the EAS Hosting dashboard.
### Crashes
A crash is any uncaught error that is thrown while a request was handled, which prevented a response from being returned, for example, `throw new Error("An error!")`. Crashes may be viewed on the [Hosting crashes](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/crashes) page.
Crashes are grouped. If similar crashes are detected, you will see just one line item for them. The crash details will show the stack trace and metadata for the first and last known occurrence of the crash.
### Logs
All logs from API routes and server functions (`console.log`, `console.info`, `console.error`, and so on) are recorded on the deployment level logs page. Go to [Hosting deployments](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/deployments) > _select a deployment_ > **Logs**.
### Requests
Requests can be viewed on the project level at [Hosting requests](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/requests) and deployment level [Hosting Deployments](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/deployments) > _select a deployment_ > **Requests**.
This will show a list of requests against your service, with metadata (status, browser, region, duration, and more) per request. These include all requests to the service, including requests to API routes.
### Looking up a request by ID
All response headers include a `Cf-Ray` header that looks like `8ffb63895cf6779b-LHR`. The first part of this is the request ID and you may look up the request on the EAS dashboard via this ID using the filters in [Hosting Requests](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/requests).
This request ID is also displayed on any service-level error pages.
### Sampling
If a deployment receives a high amount of traffic, data that EAS Hosting records will be [downsampled](https://developers.cloudflare.com/analytics/graphql-api/sampling/). This means as your deployments receive more requests, fewer data points will be recorded, and you may not see individual requests, logs, and crashes be listed one by one. However, statistical counts, such as number of requests or crashes, will be estimated to still reflect all requests proportionally.
## Deploy with Workflows
Learn how to automate React Native CI/CD for web deployments with EAS Hosting and Workflows.
EAS Workflows is a great way to automate the React Native CI/CD pipeline for web deployment of your project to EAS Hosting with pull request (PR) previews and production deployments.
## Setup workflows
To use [EAS workflows](/eas/workflows/get-started/) to automatically deploy your project, follow the instructions in [Get started with EAS workflows](/eas/workflows/get-started/) and add the [GitHub integration](/eas/workflows/get-started/#configure-your-project) for your project.
## Create a deployment workflow
Add the following file to **.eas/workflows/deploy.yml**. This will use the production environment variables, export the web bundle, deploy your project and promote it to production whenever you push to the `main` branch.
```yml
name: Deploy
on:
push:
branches: ['main']
jobs:
deploy:
type: deploy
name: Deploy
environment: production
params:
prod: true
```
Now, whenever a commit is pushed to `main` or a PR is merged, the workflow will run to deploy your website.
You can also test this workflow by triggering it manually:
```sh
$ eas workflow:run .eas/workflows/deploy.yml
```
# Reference
## Caching with EAS Hosting deployments
Learn how caching works on EAS Hosting.
## Caching with API Routes
API routes can return `Cache-Control` directives that will be used by EAS Hosting to cache the response appropriately according to the value of cache directives.
```js
export async function GET(request) {
return Response.json({ ... }, {
headers: {
'Cache-Control': 'public, max-age=3600'
},
});
}
```
`Cache-Control` directives present in the response will be used by EAS Hosting to cache the response as specified. For example, if the `Response` specifies a cache directive with `max-age` set to 1800 seconds, the response will be cached for the specified amount of time before the API route is invoked again.
For more details about `Cache-Control` directives, refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
## Cache-Control directives
`Cache-Control` headers may be sent as part of a request's and a response's headers and are a string of comma-separated settings.
When a request sends a `Cache-Control` header, it typically sends directives that constrain how a cached response may be delivered. If it's sent as a response header, it specifies to EAS Hosting how a response will be cached and how often it will be revalidated by re-invoking an API route.
If a cache directive accepts a parameter, the directive is followed by an equal sign and the parameter's value, for example, `max-age=3600`. If the directive does not accept a parameter, it's listed without a value, for example, `public`.
If multiple cache directives are passed, each is separated from the last by a comma, for example, `public, max-age=3600`.
## Cacheability
Several response directives determine whether a cached response may be cached or returned to a client:
- `public` — Indicates any cache (including EAS Hosting) may store the response. Without it, it's implied that the response is not shared between multiple requests.
- `private` — Indicates the response is intended for a single user and may only be cached by a browser.
- `no-store` or `no-cache` — Indicates that this response may never be cached or stored.
For example, specifying `public, max-age=3600` specifies that EAS Hosting is (additionally to a user's browser) allowed to store the response for 3600 seconds. However, `private, max-age=3600` means only the user's browser may store the response for 3600 seconds, while EAS Hosting will not cache it.
Responses to requests with no `Authorization` header set and are either for the `HEAD` or `GET` request methods are automatically considered publicly cacheable.
To differentiate between what a browser and EAS Hosting may cache, the `s-maxage` directive may be used. For example, responding with the `s-maxage=3600` directive will allow EAS Hosting to cache the response for 3600 seconds, while the user's browser won't cache it at all.
## Header names
As seen above, the Cache-Control header is accepted and understood both by browsers and EAS Hosting. To customize caching for EAS Hosting more granularly, and separately from the user's browser, you can respond with a CDN-Cache-Control header. When this header is used, it implicitly adds public to your directives and forces EAS Hosting to cache the response according to your directives.
```js
export async function GET(request) {
return Response.json({ ... }, {
headers: {
'Cache-Control': 'no-store', // browsers should never store the response
'CDN-Cache-Control': 'max-age=3600', // EAS Hosting should cache for 3600s
},
});
}
```
## Expiration directives
- `max-age` is used to specify how long a response is cached until it's considered stale
- `s-maxage` is used to indicate only to EAS Hosting how long it should cache a response
- `no-cache` is equivalent to specifying a max-age of zero
- `immutable` is used to indicate that the response is indefinitely cacheable and should be cached for as long as possible and is never considered stale.
Additionally, two newer cache control directives can be used to determine how stale responses may be used for longer than the `max-age` specified for them.
- `stale-while-revalidate` specifies a stale time period for a response. After a cached response is considered stale, it allows the response to still be returned to clients for the specified timeframe, while re-validating the request in the background.
- For example, `max-age=1800, stale-while-revalidate=3600` specifies that the response is cached for 1800 seconds. After 1800 seconds, if a new request is made for this response, it is returned if the request is made within 3600 seconds, but the request will also be sent onwards to your API route in the background.
- `stale-if-error` specifies a stale time period for a response to be returned if the underlying API route fails unexpectedly. This is useful to make an API route fault-tolerant and applies when your API route crashes with a runtime error or returns a `500`, `502`, `503` or `504` response status.
- For example, `max-age=1800, stale-if-error=3600` specifies that the response is cached for 1800 seconds. After 1800 seconds, if your API route responds with an error, the stale cached response is sent to clients instead of the error.
## Request directives
`Cache-Control` headers can be sent as part of a request's headers and will affect how EAS Hosting chooses to return cached responses.
- `only-if-cached` will only return a response if it's cached, and otherwise abort the request with a `504` response (with a `must-revalidate` directive)
- `no-store`, `no-cache`, or `max-age=0` will skip cached responses and always force EAS Hosting to ignore its request cache
- `min-fresh` will skip a cached response if it's older than the specified value. For example, `min-fresh=360` will prevent a cached response from being returned if it's been cached for longer than 360 seconds.
Additionally, `max-stale` and `stale-if-error` may be sent as part of the request's cache directive and limit the stale time of cached responses. However, remember that this doesn't override how long a request is cached, so this may only be used to **reduce** the amount of acceptable staleness of a cached response.
- `max-stale` specifies a maximum time that is acceptable for a client to accept a cached response. For example, if a response was cached using the `stale-while-revalidate=3600` directive, a request may specify `max-stale=1800` to instead only accept a stale response with a maximum age of 1800 seconds (in its stale period, not `max-age`)
- `stale-if-error` may be used to customize the period for which stale responses are accepted if the API route would otherwise respond with an error.
For both directives, if the server-side response was cached for a shorter amount of time than the specified `max-stale` or `stale-if-error` periods on top of the response's `max-age`, then these directives will do nothing.
## Request methods
In addition to caching `GET` and `HEAD` requests, EAS Hosting also supports caching `POST` requests.
Provided a `POST` request with a request body smaller than 1MB is sent, your response may specify a `Cache-Control` header with the `public` directive to mark the request as cacheable.
## The `Expires` header
EAS Hosting also supports caching using the older [`Expires` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires).
As this won't mark the response as publicly cacheable, it's typically only used for unauthenticated `GET` responses. It may specify an HTTP Date value until a response is cached. After the specified timestamp, the response is considered stale.
## The `Vary` header
By default:
- a `GET` or `HEAD` request is only cached by its URL
- a `POST` request is only cached by its URL and request body
However, you can use the `Vary` header to specify that the request should be using request headers as a cache key. For example, if an API route responds with `Vary: custom-header` , then the cached response will only be used if the request's `custom-header` header value matches the cached request's `custom-header` value.
## CORS caching
For many web requests, the browser will make CORS requests with the `OPTIONS` method to determine access control settings for a route.
These requests are cacheable using the special `Access-Control-Max-Age` header. For example, `Access-Control-Max-Age: 3600` will cache the `OPTIONS` response for 3600 seconds, which applies to both browsers and the EAS Hosting cache. This prevents excessive requests made by browsers and prevents your API route from being called excessively often for CORS requests.
## Asset Caching
For any assets your deployment responds with, a default cache time of 3600 seconds will be applied for browser caches. To improve performance, per-deployment assets are cached indefinitely internally. Since deployments are immutable, this doesn't affect you.
EAS Hosting will ignore its cached assets when you assign a new deployment to an alias. For example, when you promote a new deployment to production, the cache will be ignored and your asset responses should switch over to your new deployment instantly.
## Billing and metrics
EAS Hosting bills per request (in units of 1M requests). However, cached requests **still count** against your quota and you will be charged for requests, even if they are cached by EAS Hosting.
Metrics are not affected by caching. Cached requests will be logged like any other request, and the metrics in the EAS dashboard will also reflect and represent cached requests.
## Default responses and headers
Defaults that are added automatically on requests when using EAS Hosting.
**EAS Hosting** applies several defaults to your deployment that are supposed to help you and reduce the amount of code you have to add yourself for simple API routes.
## Asset responses
An asset response contains additional metadata headers for browsers, mostly for caching.
A default `ETag` header is added to all asset responses to allow browsers to re-validate their caches using `if-none-match` request headers.
## CORS responses
By default, if an API route does not handle `OPTIONS` requests, EAS Hosting will automatically respond with a default CORS response.
This default is very permissible and generally allows all browsers to make requests to the API route. **If you don't want this**, handle `OPTIONS` requests in API routes yourself.
The following headers will be sent by default:
```txt
Access-Control-Allow-Origin:
Access-Control-Allow-Headers: ,
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Access-Control-Max-Age: 3600
Vary: Origin, Access-Control-Request-Headers
```
These headers will allow any client to make a request from any origin, with any headers, with credentials, and cache the `OPTIONS` response for 3600 seconds.
More information on [preflight `OPTIONS` requests can be found in the MDN documentation](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request).
## Strict-Transport-Security header
This header tells browsers to only access a URL with the HTTPS protocol in the future. EAS Hosting automatically adds this header if it's missing.
Its default value is set to `max-age=31536000; includeSubDomains; preload`.
For more information on why this header is a good default, improves security, and performance, [read this article on `web.dev`](https://web.dev/blog/bbc-hsts) and read more about [Strict-Transport-Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) header in the MDN documentation.
## Common version headers
By default, EAS Hosting will remove and not forward any `X-Powered-By` and `X-Aspnet-Version` headers. With API routes, this header does not serve much of a purpose and we don't recommend you add alternative headers like `X-Powered-By` to your API routes as it unnecessarily exposes internal information on the code you're running.
## Frame Content-Security Policy (CSP)
By default, browsers allow any webpage to render in an iframe. Unfortunately, this allows phishing attacks such as clickjacking which is used by malicious actors to overlay a page with their own controls and inputs, to steal credentials or coerce users to perform certain actions.
To prevent this, EAS Hosting adds a `frame-ancestors 'self'` directive by default, telling browsers to not embed EAS Hosting in iframes on other domains that you don't control.
This is equivalent to setting the (older) `X-Frame-Options` header to `SAMEORIGIN`.This default directive is only applied to responses with `text/html` content types, so it will only be applied to HTML pages.
If your API routes respond with custom `X-Frame-Options` headers, these headers will automatically be converted to `Content-Security-Policy` directives in your response.
## Crash pages
If your API route throws an unhandled JavaScript error, this is treated as a "crash" since your API route was unable to deliver an error.
EAS Hosting will respond with an error page in these cases. The error page will be rendered as an HTML response, if the `Accept: text/html` request header was sent. Otherwise, it will only respond with a plaintext response.
## Request headers
**EAS Hosting** will add a couple of headers to every request, before it's forwarded to your API routes. These headers generally add more information about who made the request.
### IP headers
The request contains several headers to identify the IP address of the user's device that made the request:
- `Forwarded` is a standard header that delivers a comma-separated list of forwarder IPs for the given request. See MDN documentation on [HTTP `Forwarded` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) for more information.
- `X-Forwarded-For` is a de-facto standard header that is set to a comma-separated list of the forwarder IPs for a given request without any added information.
- `X-Forwarded-For` contains a list of protocols used to forward the request, typically it's set to `https`
- `X-Real-IP` contains only the original request's IP address
For example, to retrieve the IP address of the user's browser that is calling your API route, read the `X-Real-IP` header from the request:
```js
export async function GET(request) {
const ip = request.headers.get('X-Real-IP');
}
```
### Geo headers
The request also contains several headers containing geographical information about where the request came from:
- `eas-colo` contains the Cloudflare code for the data center that handled your request. For example, `lhr`.
- `eas-ip-continent` contains the continent code of the current request:
- `AF` for Africa
- `AN` for Antarctica
- `AS` for Asia
- `EU` for Europe
- `NA` for North America
- `OC` for Oceania
- `SA` for South America.
- `eas-ip-country` contains the ISO-3166 Alpha 2 country code. This is at most two letters long. For example, `US` or `JP`.
- `eas-ip-region` contains the ISO-3166-2 region code for the request. This value is a maximum of three characters long. However, it can vary based on how region codes in a specific country work for the requested origin.. It could comprise one to three digits, one to three letters, or any other combination.
- `eas-ip-city` may contain a human-readable name of a city. For example `London` or `Chicago`.
- `eas-ip-latitude` and `eas-ip-longitude` contain an approximate latitude and longitude for the request.
- `eas-ip-timezone` contains a best guess of the timezone the request originated in. For example, `Europe/London`
- `eas-ip-eu` is set to `1` when the request likely originated in the jurisdictional area of the European Union.
## EAS Hosting worker runtime
Learn about EAS Hosting worker runtime and Node.js compatibility.
## Caching with API Routes
EAS Hosting is built on [Cloudflare Workers](https://developers.cloudflare.com/workers/), a modern and powerful platform for serverless APIs that's been built for seamless scalability, high reliability, and exceptional performance globally.
The Cloudflare Workers runtime runs on the V8 JavaScript engine, the same powering JavaScript in Node.js and Chromium. However, its runtime has a few key differences from what you might be used to in traditional serverless Node.js deployments.
Instead of each request running in a full JavaScript process, Workers are designed to run them in small V8 isolates, a feature of the V8 runtime. Think of them as micro-containers in a single JavaScript process.
For more information on how Workers work, see [Cloudflare Workers](https://developers.cloudflare.com/workers/reference/how-workers-works/) documentation.
## Node.js compatibility
Cloudflare is part of [Winter CG](https://wintercg.org/), is more similar to the JavaScript environments in browsers and service workers rather than in Node.js. Restrictions like these provide a leaner runtime than Node.js, which is still familiar. This common runtime is a minimal standard supported by many JavaScript runtime these days.
This means, many Node.js APIs that you might be used to or some dependencies you utilize, aren't directly available in the EAS Hosting runtime. To ease this transition, as not all dependencies will have first-class support for Web APIs yet, Node.js compatibility modules exist and can be used in your API routes.
### Node.js shim modules
The list of compatibility modules for Node.js APIs natively supported by Cloudflare's runtime are:
- `assert`
- `async_hooks`
- `buffer`
- `crypto` (With some limitations and missing exports)
- `diagnostics_channel`
- `events`
- `path`, `path/posix`, and `path/win32`
- `process`
- `querystring`
- `stream`, `stream/consumers`, `stream/promises`, and `stream/web`
- `string_decoder`
- `url`
- `util`
- `zlib`
Additionally, EAS Hosting provides JavaScript-level shims (or rather polyfills) for the following modules:
- `http`
- `https`
- `tty`
These modules generally provide a lower-accuracy polyfill of their Node.js counterparts. For example, the `http` and `https` modules only provide thin Node.js compatibility wrappers around the `fetch` API and cannot be used to make arbitrary HTTP requests.
The following modules are mocked but won't provide any functionality:
- `child_process`
- `net`
Any of the above Node.js modules can be used in API routes or dependencies of your API routes as usual and will use appropriate compatibility modules.
Any modules that aren't mentioned here are unavailable, and your code and none of your dependencies should rely on them being provided.
> More Node compatibility shims may be added in the future, but all Node APIs that are not documented in these non-exhaustive lists are expected to work.
## Globals
- The `process` global is present with mock values set as appropriate to match Node.js
- `process.env` is available and will be populated with your deployment's environment variables
- `process.stdout` and `process.stderr` will convert the output written to them to `console.log` and `console.error` calls
- `setImmediate` and `clearImmediate` are available as shims around `setTimeout` and `clearTimeout`
- The `Buffer` and `EventEmitter` globals are available and set to their Node.js shims
- `global` is available and set to `globalThis`
- `require()` is available in CommonJS modules but `require.cache` is unavailable
# EAS Submit
## EAS Submit
EAS Submit is a hosted service for uploading and submitting an app binary to the app stores.
**EAS Submit** is a hosted service for submitting your app binaries to the Apple App Store and the Google Play Store.
It makes submitting your apps simple and easy by creating and handling submission credentials and running the submission process. You can trigger a submission from a CLI command, after a build is finished, or from a CI/CD service. It's the fastest way to take Android and iOS builds and submit them to the app stores.
### Get started
## Submit to the Google Play Store
Learn how to submit your app to the Google Play Store from your computer and CI/CD services.
This guide outlines how to submit your app to the Google Play Store from your computer or from a CI/CD service.
## Submitting your app from your computer
A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up for a Google Play Developer account on the [Google Play Console sign-up page](https://play.google.com/apps/publish/signup/).
Create an app by clicking **Create app** in the [Google Play Console](https://play.google.com/apps/publish/).
EAS requires you to upload and configure a Google Service Account Key to submit your Android app to the Google Play Store. You can create one with the [uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) guide.
Install EAS CLI and login with your Expo account:
```sh
$ npm install -g eas-cli && eas login
```
Include your app's package name in **app.json**:
```json app.json
{
"android": {
"package": "com.yourcompany.yourapp"
}
}
```
You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform android --profile production
```
Alternatively, you can build the app on your own computer with `eas build --platform android --profile production --local` or with Android Studio.
You have to upload your app manually at least once. This is a limitation of the Google Play Store API.
Learn how with the [first submission of an Android app](https://expo.fyi/first-android-submission) guide.
Once you have completed all the prerequisites, you can start the submission process.
Run the following command to submit a build to the Google Play Store:
```sh
$ eas submit --platform android
```
The command will lead you step by step through the process of submitting the app. You can configure the submission process by adding a submission profile in **eas.json**. Learn about all the options you can provide in the [eas.json reference](/eas/json/#android-specific-options-1).
To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it's built:
```sh
$ eas build --platform android --auto-submit
```
Learn more about the `--auto-submit` flag in the [automate submissions](/build/automate-submissions/) guide.
## Submitting your app using CI/CD services
A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up for a Google Play Developer account on the [Google Play Console sign-up page](https://play.google.com/apps/publish/signup/).
Create an app by clicking **Create app** in the [Google Play Console](https://play.google.com/apps/publish/).
EAS requires you to upload and configure a Google Service Account Key to submit your Android app to the Google Play Store. You can create one with the [uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) guide.
Install EAS CLI and login with your Expo account:
```sh
$ npm install -g eas-cli && eas login
```
Include your app's package name in **app.json**:
```json app.json
{
"android": {
"package": "com.yourcompany.yourapp"
}
}
```
Then, you'll need to provide a submission profile in **eas.json** that includes your app's `serviceAccountKeyPath`:
```json eas.json
{
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json"
}
}
}
}
```
There are additional options available for Android submissions. Learn more from the [eas.json reference](/eas/json/#android-specific-options-1).
You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform android --profile production
```
Alternatively, you can build the app on your own computer with `eas build --platform android --profile production --local` or with Android Studio.
You have to upload your app manually at least once. This is a limitation of the Google Play Store API.
Learn how with the [first submission of an Android app](https://expo.fyi/first-android-submission) guide.
Once you have completed all the prerequisites, you can set up a CI/CD pipeline to submit your app to the Google Play Store.
### Use EAS Workflows CI/CD
You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically.
1. Create a workflow file named **.eas/workflows/submit-android.yml** at the root of your project.
2. Inside **submit-android.yml**, you can use the following workflow to kick off a job that submits an Android app:
```yaml .eas/workflows/submit-android.yml
on:
push:
branches: ['main']
jobs:
build_android:
name: Build Android app
type: build
params:
platform: android
profile: production
# @info #
submit_android:
name: Submit to Google Play Store
type: submit
params:
platform: android
build_id: ${{ needs.build_android.outputs.build_id }}
# @end #
```
The workflow above will build the Android app and then submit it to the Google Play Store.
### Use other CI/CD services
You can use other CI/CD services to submit your app with EAS Submit, like GitHub Actions, GitLab CI, and more by running the following command:
```sh
$ eas submit --platform android --profile production
```
This command requires a [personal access token](/accounts/programmatic-access/#personal-access-tokens) to authenticate with your Expo account. Once you have one, provide the `EXPO_TOKEN` environment variable in the CI/CD service, which will allow the `eas submit` command to run.
## Submit to the Apple App Store
Learn how to submit your app to the Apple App Store from your computer and CI/CD services.
This guide outlines how to submit your app to the Apple App Store from your computer or from a CI/CD service.
## Submitting your app from your computer
An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/).
Include your app's bundle identifier in **app.json**:
```json app.json
{
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp"
}
}
```
Install EAS CLI and login with your Expo account:
```sh
$ npm install -g eas-cli && eas login
```
You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform ios --profile production
```
Alternatively, you can build the app on your own computer with `eas build --platform ios --profile production --local` or with Xcode.
Once you have completed all the prerequisites, you can start the submission process.
Run the following command to submit a build to the Apple App Store:
```sh
$ eas submit --platform ios
```
The command will lead you step by step through the process of submitting the app. You can configure the submission process by adding a submission profile in **eas.json**:
```json eas.json
{
"submit": {
"production": {
"ios": {
"ascAppId": "your-app-store-connect-app-id"
}
}
}
}
```
Note: How to find
---
1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and select your team.
2. Navigate to the [Apps](https://appstoreconnect.apple.com/apps).
3. Click on your app.
3. Ensure that **App Store** tab is active.
4. On the left pane, under the **General** section, select **App Information**.
5. Your app's `ascAppId` can be found under **General Information** section under **Apple ID**.
---
Learn about all the options you can provide in the [eas.json reference](/eas/json/#ios-specific-options-1).
To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it's built:
```sh
$ eas build --platform ios --auto-submit
```
Learn more about the `--auto-submit` flag in the [automate submissions](/build/automate-submissions/) guide.
## Submitting your app using CI/CD services
An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/).
Include your app's bundle identifier in **app.json**:
```json app.json
{
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp"
}
}
```
Run the following command to configure your App Store Connect API Key:
```sh
$ eas credentials --platform ios
```
The command will prompt you to select the type of credentials you want to configure.
1. Select the `production` build profile
2. Log in with your Apple Developer account and follow the prompts
3. Select **App Store Connect: Manage your API Key**
4. Select **Set up your project to use an API Key for EAS Submit**
Note: Do you want to use your own credentials?
---
**App Store Connect API Key:** Create your own [API Key](https://expo.fyi/creating-asc-api-key) then set it with the `ascApiKeyPath`, `ascApiKeyIssuerId`, and `ascApiKeyId` fields in **eas.json**.
**App Specific Password:** Provide your [password](https://expo.fyi/apple-app-specific-password) and Apple ID Username by passing them in with the `EXPO_APPLE_APP_SPECIFIC_PASSWORD` environment variable and `appleId` field in **eas.json**, respectively.
---
Then, you'll need to provide a submission profile in **eas.json** that includes the following fields:
```json eas.json
{
"submit": {
"production": {
"ios": {
"ascAppId": "your-app-store-connect-app-id"
}
}
}
}
```
Note: How to find
---
1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and select your team.
2. Navigate to the [Apps](https://appstoreconnect.apple.com/apps).
3. Click on your app.
3. Ensure that **App Store** tab is active.
4. On the left pane, under the **General** section, select **App Information**.
5. Your app's `ascAppId` can be found under **General Information** section under **Apple ID**.
---
Learn about all the options you can provide in the [eas.json reference](/eas/json/#ios-specific-options-1).
You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/):
```sh
$ eas build --platform ios --profile production
```
Alternatively, you can build the app on your own computer with `eas build --platform ios --profile production --local` or with Xcode.
Once you have completed all the prerequisites, you can set up a CI/CD pipeline to submit your app to the Apple App Store.
### Use EAS Workflows CI/CD
You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically.
1. Create a workflow file named **.eas/workflows/submit-ios.yml** at the root of your project.
2. Inside **submit-ios.yml**, you can use the following workflow to kick off a job that submits an iOS app:
```yaml .eas/workflows/submit-ios.yml
on:
push:
branches: ['main']
jobs:
build_ios:
name: Build iOS app
type: build
params:
platform: ios
profile: production
# @info #
submit_ios:
name: Submit to Apple App Store
type: submit
params:
platform: ios
build_id: ${{ needs.build_ios.outputs.build_id }}
# @end #
```
The workflow above will build the iOS app and then submit it to the Apple App Store.
### Use other CI/CD services
You can use other CI/CD services to submit your app with EAS Submit, like GitHub Actions, GitLab CI, and more by running the following command:
```sh
$ eas submit --platform ios --profile production
```
This command requires a [personal access token](/accounts/programmatic-access/#personal-access-tokens) to authenticate with your Expo account. Once you have one, provide the `EXPO_TOKEN` environment variable in the CI/CD service, which will allow the `eas submit` command to run.
## Manual submissions
If you ever need to submit your build without going through EAS Submit, for example, if the service is temporarily unavailable for maintenance, you can upload to the Apple App Store manually from a macOS device.
Note: How to upload to the Apple App Store manually from a macOS device
---
#### Creating an entry on App Store Connect
Start by creating an app profile in App Store Connect, if you haven't already:
1. Go to [App Store Connect](https://appstoreconnect.apple.com) and sign in. Make sure you have accepted any legal notices or terms at the top of the page.
2. Click the blue plus button by the Apps header, then click **New App**.
3. Add your app's name, language, bundle identifier, and SKU (this isn't seen by end users, it can be any unique string. A common choice is your app's bundle identifier, for example, "com.company.my-app").
4. Click **Create**. If this succeeds, then you have created your application record.
#### Uploading with Transporter
Finally, you need to upload the IPA to the Apple App Store.
1. Download [**Transporter** from the App Store](https://apps.apple.com/app/transporter/id1450874784).
2. Sign in with your Apple ID.
3. Add the build either by dragging the IPA file directly into the Transporter window or by selecting it from the file dialog opened with **+** or **Add App** button.
4. Submit it by clicking the **Deliver** button.
This process can take a few minutes, then another 10-15 minutes of processing on Apple's servers. Afterward, you can check the status of your binary in App Store Connect:
1. Visit [App Store Connect](https://appstoreconnect.apple.com), select **My Apps**, and click on the app entry you created earlier.
2. Scroll down to the **Build** section and select your newly uploaded binary.
---
## Configure EAS Submit with eas.json
Learn how to configure your project for EAS Submit with eas.json.
**eas.json** is the configuration file for EAS CLI and services. It is generated when the [`eas build:configure` command](/build/setup/#configure-the-project) runs for the first time in your project and is located next to **package.json** at the root of your project. Even though **eas.json** is not mandatory for using EAS Submit, it makes your life easier if you need to switch between different configurations.
## Production profile
Running `eas submit` without specifying a profile name will use the `production` profile if it is already defined in **eas.json** to configure the submission. If no values exist in the `production` profile, EAS CLI will prompt you to provide the values interactively.
The `production` profile shown below is required to run Android and iOS submissions in a CI/CD process, like with [EAS Workflows](/eas/workflows/):
```json eas.json
{
"cli": {
"version": ">= 0.34.0"
},
"submit": {
"production": {
"android": {
"serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json",
"track": "internal"
},
"ios": {
"ascAppId": "your-app-store-connect-app-id"
}
}
}
}
```
Learn more about the values you can set with the [Android specific options](/eas/json/#android-specific-options) and the [iOS specific options](/eas/json/#ios-specific-options). You can also learn how to submit to the [Apple App Store](/submit/ios) and the [Google Play Store](/submit/android).
## Multiple profiles
The JSON object under `submit` can contain multiple submit profiles. Each profile under `submit` can have an arbitrary name as shown in the example below:
```json eas.json
{
"cli": {
},
"build": {
// EAS Build configuration
},
"submit": {
"android": {
},
"ios": {
}
},
"extends": "SUBMIT_PROFILE_NAME_1",
"android": {
}
},
}
}
```
When you select a build for submission, it chooses the profile that is used for the selected build. If the profile does not exist, it selects the default `production` profile.
You can also use EAS CLI to pick up another `submit` profile by specifying it with a parameter. For example, `eas submit --platform ios --profile submit-profile-name`.
## Share configuration between `submit` profiles
A `submit` profile can extend another profile using the `extends` key.
For example, in the `preview` profile you may have `"extends": "production"`. This makes the `preview` profile inherit the configuration of the `production` profile.
You can keep chaining profile extensions up to the depth of 5 as long as you avoid making circular dependencies.
## Next step
# EAS Update
## EAS Update
An introduction to EAS Update which is a hosted service for projects using the expo-updates library.
**EAS Update** is a hosted service that serves updates for projects using the [`expo-updates`](/versions/latest/sdk/updates) library.
EAS Update makes fixing small bugs and pushing quick fixes a snap in between app store submissions. It accomplishes this by enabling an app to update its own non-native pieces (such as JS, styling, and images) over-the-air.
All apps that include the `expo-updates` library have the ability to receive updates.
- To start using EAS Update, continue to the [Getting Started](/eas-update/getting-started) guide.
- For a complete tutorial of using EAS Update with other EAS services, refer to the [EAS Tutorial](/tutorial/eas/introduction/).
## JS API for update management
The updates [JavaScript API](/versions/latest/sdk/updates/) includes a React hook called `useUpdates()`. This hook provides detailed information about the currently running update and any new updates that are available or have been downloaded. In addition, you can view any errors that were encountered during the update process to help you debug any issues while the app is attempting to update.
The API also provides methods such as `checkForUpdateAsync()` and `fetchUpdateAsync()` which allows you to control when your app checks for and downloads updates.
## Insight tracking
You'll get a [deployments dashboard](https://expo.dev/accounts/[account]/projects/[project]/deployments) that helps visualize which updates are being sent to builds. Updates work in concert with [insights](/eas-insights/introduction/) to provide data on the adoption rates of your updates with your users.
## Republish for reverting mistakes
If an update isn't performing as expected, you can [republish](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch) a previous, stable version on top of the problematic one, much like a new "commit" in version control systems.
## Get started
## Get started with EAS Update
Learn how to get started with the setup required to configure and use EAS Update in your project.
Setting up EAS Update allows you to push critical bug fixes and improvements that your users need right away. This guide will walk you through the process of setting up EAS Update in a new or existing project.
> **info** If you plan to use EAS Update with EAS Build, we recommend following the [EAS Build setup guide](/build/setup/) before proceeding here. That said, [you can use EAS Update without any other EAS services](/eas-update/standalone-service/).
## Prerequisites
Note: An Expo user account
---
EAS Update is available to anyone with an Expo account, regardless of whether you pay for EAS or use the Free plan. You can sign up at [expo.dev/signup](https://expo.dev/signup).
Paid subscribers can publish updates to more users and use more bandwidth and storage. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing).
---
Note: A React Native project
---
Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide.
Run the following command to create a new project:
```sh
$ npx create-expo-app my-app
```
EAS Update also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools.
---
Note: Your project must use Expo CLI and extend the Expo Metro Config
---
If you already run your project with `npx expo [command]` (for example, if you created it with `npx create-expo-app`) then you're all set.
If you don't have the `expo` package in your project yet, then install it by running the command below and [opt in to using Expo CLI and Metro Config](/bare/installing-expo-modules/#configure-expo-cli-for-bundling-on-android-and-ios):
```sh
$ npx install-expo-modules@latest
```
If the command fails, refer to the [Installing Expo modules](/bare/installing-expo-modules/#manual-installation) guide.
---
Step 1:
## Install the latest EAS CLI
EAS CLI is the command line app you will use to interact with EAS services from your terminal. To install it, run the command:
```sh
$ npm install --global eas-cli
```
You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version.
> We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation.
Step 2:
## Log in to your Expo account
If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in:
```sh
$ eas login
```
You can check whether you are logged in by running `eas whoami`.
Step 3:
## Configure your project
Navigate to your project directory in your terminal and run the following command:
```sh
$ eas update:configure
```
Note: What does this command do?
---
The `eas update:configure` command will update your **app.json** file with the `runtimeVersion` and `updates.url` properties, and add the `extra.eas.projectId` field if your project wasn't using any EAS services previously.
When you run `eas update:configure` in a project that doesn't use [CNG](/workflow/continuous-native-generation/), you'll see the following changes to your native projects:
#### Android
Inside the **android/app/src/main/AndroidManifest.xml** file, you'll see the following additions:
```xml android/app/src/main/AndroidManifest.xml
```
The `EXPO_UPDATE_URL` value should contain your project's ID.
Inside **android/app/src/main/res/values/strings.xml**, you'll see the `expo_runtime_version` string entry in the `resources` object:
#### iOS
Inside **ios/project-name/Supporting/Expo.plist**, you'll see the following additions:
```xml ios/project-name/Supporting/Expo.plist
EXUpdatesRuntimeVersion1.0.0EXUpdatesURLhttps://u.expo.dev/your-project-id
```
The `EXUpdatesURL` value should contain your project's ID.
---
Step 4:
## Configure the update channel
The channel property on a build allows you to point updates at specific types of builds. For example, it allows you to publish updates to a preview build without impacting your production deployment.
**If you are using EAS Build**, `eas update:configure` will set the update `channel` property on the `preview` and `production` profiles in **eas.json**. Set them manually if you use different profile names.
Note: Example channel configuration in eas.json
---
```json eas.json
{
"build": {
"preview": {
"channel": "preview"
// ...
},
"production": {
"channel": "production"
// ...
}
}
}
```
---
**If you are not using EAS Build**, then you will need to configure the channel in **app.json** or in your native projects, depending on whether you use [CNG](/workflow/continuous-native-generation/). When you create a build for a different environment, you will need to modify the channel to ensure your build pulls updates from the correct channel. Learn more using [EAS Update as a standalone service](/eas-update/standalone-service/).
Note: Configure update channels in app.json
---
If you use Continuous Native Generation (CNG), then you can configure the channel with the `updates.requestHeaders` property in your **app.json**:
```json app.json
{
"expo": {
"updates": {
"requestHeaders": {
"expo-channel-name": "your-channel-name"
}
}
}
}
```
The configuration will be applied the next time you run `npx expo prebuild`.
---
Note: Configure update channels in an Android native project
---
In **AndroidManifest.xml**, you will need to add replace `your-channel-name` with the channel that matches your project:
```xml android/app/src/main/AndroidManifest.xml
```
---
Note: Configure update channels in an iOS native project
---
In **Expo.plist**, you'll need to add the following, replacing `your-channel-name` with the channel that matches your project:
```xml ios/project-name/Supporting/Expo.plist
EXUpdatesRequestHeadersexpo-channel-nameyour-channel-name
```
---
Step 5:
## Create a build for the project
You need to create a build for Android or iOS. We recommend creating a build with the `preview` build profile first. See [Create your first build](/build/setup/) on how to get started and set up [Internal distribution](/build/internal-distribution/#setting-up-internal-distribution) for your device or simulator.
Once you have a build running on your device or a simulator, you are ready to send an update.
Step 6:
## Make changes locally
After creating the build, you are ready to iterate on the project. Start a local development server with the following command:
```sh
$ npx expo start
```
Then, make any desired changes to your project's JavaScript, styling, or image assets.
Step 7:
## Publish an update
Publishing an update allows:
- Fixing bugs and quickly updating non-native parts of a project instead of creating a new build
- [Sharing a preview version of an app](/review/overview/) using internal distribution
To publish an update with changes from your project, use the `eas update` command, and specify a name for the channel and a `message` to describe the update:
```sh
$ eas update --channel [channel-name] --message "[message]"
```
Note: How does publishing an update work?
---
When you publish an update with the `eas update` command, it generates a new update bundle and uploads it to the EAS servers. The channel name is used to locate the correct branch to publish a new update from other update branches. It is similar to how Git commit works, where every commit is on a Git branch.
For example, when an app is set to pull updates from the `preview` channel, you can publish an update for that build with `eas update --channel preview`. This will create a branch (called `preview` by default) on the `preview` channel. Behind the scenes, this command runs `npx expo export` to generate a **dist** directory and create a local update bundle. This update bundle is uploaded to EAS Update servers.
---
Step 8:
## Test the update
After the update is uploaded to EAS Update, you can use one of the following methods to test the update:
- Use the Extensions tab in a [development build](/eas-update/expo-dev-client/) to load the update.
- Use [Expo Orbit](/review/with-orbit/) to install and launch the update in a development build.
- Implement a custom strategy with [Updates API](/versions/latest/sdk/updates/) and [app config](/versions/latest/config/app/#updates) to load updates in the app programmatically.
- Manually test the update by force closing and reopening a release build of your app up to two times to download and apply the update. Updates for non-development builds (preview or production) are automatically downloaded to the device in the background when the app starts up and makes a request for any new updates. The update will be applied after it is downloaded and the app is restarted.
Note: Something not working?
---
If your app is not updating as expected, see the [debugging guide](/eas-update/debug/) for techniques to validate your configuration.
---
## Next steps
# Preview
## Preview updates
Learn how to preview updates in development, preview, and production builds.
Before deploying an update to production, you will often want to test it in a production-like environment. This guide will outline different approaches for previewing updates, and link out to more detailed guides for each approach.
## Previewing updates in development builds
Development builds are a great way to preview updates from pull requests, directly from the EAS dashboard, or from the built-in UI provided by the `expo-dev-client` library.
## Previewing updates in preview builds
Non-technical users will typically not want to interact with a development build, and they will want to test changes from a preview build on an [App store testing track](/review/overview/#app-store-testing-tracks) or [internal distribution](/review/overview/#internal-distribution-with-eas-build).
If your team is smaller, it may be sufficient to deploy a single preview build at a time to an app store testing track or internal distribution. You can then publish updates to the channel that is used by that preview build. [Learn more about preview builds](/review/overview/).
Alternatively, you can build a mechanism into your preview build that allows users to select a different update or channel to load. This can be useful in cases where the [app runtime](/eas-update/runtime-versions/) doesn't change often, and many different updates can be loaded in the same app. [Learn more](/eas-update/override/).
## Previewing updates in production builds
Before deploying an update to all end-users, some teams will want to first roll it out in production to a small set of internal users. One way this can be accomplished is by [overriding the update channel](/eas-update/override/) at runtime for a known subset of users. **Be sure to note the [security considerations](/eas-update/override/#security-considerations) before proceeding down this path.** Additionally, it is not recommended to use this approach for non-internal users because it makes it possible to get the app into a state where it must be uninstalled and reinstalled.
Another approach is to use a deployment pattern like the [Persistent Staging Flow](/eas-update/deployment-patterns/#persistent-staging-flow), which involves always having a version of your production app that points to a staging channel.
## Override update configuration at runtime
Learn how to override the update URL and request headers at runtime to control which update is loaded on the client side.
> **warning** The features described in this guide are available in Expo SDK 52 with the `expo-updates` version 0.27.0 and later. Using the `disableAntiBrickingMeasures` option is not recommended for production apps, it is currently primarily intended for preview environments.
The typical way to use EAS Update is to have a single update URL and a set of request headers (such as update channel name) embedded in a build of your app. To control which update is loaded you make changes on the server through the `eas update` command or the EAS dashboard. For example, you publish a new update to a channel that your build is pointing to, then the build fetches that update on the next launch. Updates published to a channel different from the one your build is pointing to will not be downloaded with this approach.
This guide explains how you can change the update URL and request headers at runtime, making it possible to load a specific update by ID or change the channel that updates are pulled from without creating and installing a new build.
## Use cases for changing the update URL and request headers at runtime
The primary use case that this is intended for is to **enable switching between updates in a preview build**, similar to what is possible in a development build. This is useful to enable non-technical stakeholders to test and validate updates of work-in-progress (such as from a pull request or different feature branches) without having to use a development build or create a separate preview build for each change.
Another potential use case is to provide different updates to different users, for example so that a group of internal users (such as employees) receive updates before end-users. It is important to be familiar with the [security considerations](#security-considerations) before deciding to use this feature in production. In the future, we may add support for a more restricted version of the feature that would be more suitable for this use case.
## How it works
There are two relevant APIs:
1. `Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object })` - this method overrides the update URL and the request headers that are specified in **app.json** / **Expo.plist** / **AndroidManifest.xml**, such as the `expo-channel-name` header.
2. `disableAntiBrickingMeasures` - this field in the app config disables anti-bricking measures built-in to `expo-updates` which ensure subsequent updates can always be published to fix issues in previously-installed update. When you change this value, you will need to create a new build for it to take effect. **Do not enable this in your production builds.** The reason for this name is to clearly indicate that when you override the update URL/headers, we're no longer able to safely rollback to the previous update that was loaded. So, if the new update you have loaded causes the app to crash then `expo-updates`cannot automatically recover, because this field in conjunction with `setUpdateURLAndRequestHeadersOverride` will disable embedded updates and therefore there will not be any update to rollback to. The user would need to uninstall and reinstall the app. You should only use this feature in preview builds.
How to use these APIs:
1. **Override the update URL/headers, instruct user to close the app**: Somewhere in your app, you would provide a way for users to trigger the change to the URL and/or request headers. This may be in a hidden menu that only trusted users have access to, or some other mechanism depending on your use case. When the parameters are changed, notify the user that they need to close and re-open the app, such as via an alert. The `expo-updates` library, with methods like `checkForUpdateAsync()`, will not use the new overridden URL and request headers until the app is closed and reopened.
2. **The new update will be downloaded and launched on the next app open**: After the app is completely closed ("killed" and not just backgrounded) and re-opened, the update and its related assets will all be downloaded. Once they are ready, the app will launch. While it's downloading, the user will have to wait on the splash screen. We understand that waiting on the splash screen is not ideal, and we intend to improve this experience in the future if this feature is widely used. For the currently recommended use case (previews), this is likely an acceptable compromise.
## Security considerations
The anti-bricking measures that can be disabled with `disableAntiBrickingMeasures` ensure that, no matter what update is published, you can always publish another update afterwards that will be applied. By disabling the anti-bricking measures, certain categories of attacks and exploits become possible, especially around in-house (malicious employee) publishing of malicious updates. For example, an employee with the ability to publish updates could publish a malicious update that changes the update URL and request headers to point to their own server, and take over installations of the app. This risk can be mitigated, but not eliminated, by using [code signing](/eas-update/code-signing/) for production updates and limiting access to the key.
Note: Did similar usage of CodePush carry the same risk?
---
Yes. CodePush allowed developers to swap deployment keys with `sync({ deploymentKey: string })` which could be used maliciously take over an app installation in this same way.
---
## Example code
Here's an example of how you might use these APIs:
```js
import * as Updates from 'expo-updates';
// Where you call this method depends on your use case - it may make sense to
// have a menu in your preview builds that allows testers to pick from available
// pull requests, for example.
function overrideUpdateURLAndHeaders() {
Updates.setUpdateURLAndRequestHeadersOverride({
url: '...',
requestHeaders: { 'expo-channel-name': 'pr-123' },
});
alert('Close and re-open the app to load the latest version.');
}
```
```json
{
"expo": {
"updates": {
// We recommend only enabling this in preview builds.
// You can use app.config.js to configure it dynamically.
"disableAntiBrickingMeasures": true
// etc..
}
}
}
```
## Preview updates in development builds
Learn how to use the expo-dev-client library to preview a published EAS Update inside a development build.
[`expo-dev-client`](/develop/development-builds/introduction/) library allows launching different versions of a project by creating a development build. Any compatible EAS Update can be previewed in a development build.
This guide walks through the steps required to load and preview a published update inside a development build using the **Extensions** tab or constructing a specific Update URL.
## Prerequisites
- [Create a development build and install it](/develop/development-builds/create-a-build/) on your device or Android Emulator or iOS Simulator.
- Make sure your development build has the [`expo-updates` library installed](/eas-update/getting-started/#configure-your-project).
## What is an Extensions tab
When using the `expo-updates` library inside a development build, the **Extensions** tab provides the ability to load and preview a published update automatically.
### Preview an update using the Extensions tab
Step 1:
Make non-native changes locally in your project and then [publish them using `eas update`](/eas-update/getting-started/#publish-an-update). The update will be published on a branch.
Step 2:
After publishing the update, open your development build, go to **Extensions**, and tap **Login** to log in to your Expo account within the development build. This step is required for the **Extensions** tab to load any published updates associated with the project under your Expo account.
Step 3:
After logging in, an EAS Update section will appear inside the **Extensions** tab with one or more of the latest published updates. Tap **Open** next to the update you want to preview.
In the **Extensions** tab, you can view the list of all published updates for a branch. Tap the branch name in the **Extensions** tab.
## Preview an update using the Expo dashboard
You can also preview an update using the Expo dashboard by following the steps below:
- Click the published updated link in the CLI after running the command to publish an update. This will open the update's details on the **Updates** page in the Expo dashboard.
- Click **Preview**. This will open the **Preview** dialog.
- To preview the update, you can either scan the QR code with your device's camera or select a platform to [launch the update under **Open with Orbit**](/review/with-orbit/).
## Construct an update URL
As an alternative to the methods described in the previous sections, you can construct a specific URL to open your EAS Update in the development build. The URL will look like the following:
```bash
[slug]://expo-development-client/?url=[https://u.expo.dev/project-id]/group/[group-id]
# Example
my-app://expo-development-client/?url=https://u.expo.dev/675cb1f0-fa3c-11e8-ac99-6374d9643cb2/group/47839bf2-9e01-467b-9378-4a978604ab11
```
Let's break this URL to understand what each part does:
| Part of URL | Description |
| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `slug` | The project's [slug](/versions/latest/config/app/#slug) found in the app config. |
| `://expo-development-client/` | Necessary for the deep link to work with the [`expo-dev-client`](/versions/latest/sdk/dev-client/) library. |
| `?url=` | Defines a `url` query parameter. |
| `https://u.expo.dev/675cb1f0-fa3c-11e8-ac99-6374d9643cb2` | This is the updates URL, which is inside the project's app config under [`updates.url`](/versions/latest/config/app/#url). |
| `/group/47839bf2-9e01-467b-9378-4a978604ab11` | The group ID of the update. |
Once you've constructed the URL, copy and paste it directly into the development build's launcher screen under **Enter URL Manually**.
Alternatively, you can [create a QR code for the URL](/more/qr-codes/) and scan it using your device's camera. When scanned, the URL will open up the development build to the specified channel.
## Example
}
Icon={GithubIcon}
href="https://github.com/jonsamp/test-expo-dev-client-eas-update"
/>
## Github Action for PR previews
Learn how to use GitHub Actions to automate publishing updates with EAS Update.
A GitHub Action is a cloud function that runs every time an event on GitHub occurs. You can configure GitHub Actions to automate building and publishing updates when you or members of your team merge to a branch, like "production". This makes the process of deploying consistent and fast, leaving you more time to develop your app.
This guide will walk you through how to set up GitHub Actions to publish previews on pull requests.
## Publish previews on pull requests
Another common use case is to create a new update for every pull request. This allows you to test the changes in the pull request on a device before merging the code, and without having to start the project locally. Below are the steps to publish an update every time a pull request is opened:
Step 1:
Create a file path named **.github/workflows/preview.yml** at the root of your project.
Step 2:
Inside **preview.yml**, copy and paste the following snippet:
```yaml preview.yml
name: preview
on: pull_request
jobs:
update:
name: EAS Update
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Check for EXPO_TOKEN
run: |
if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then
echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions"
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18.x
cache: yarn
- name: Setup EAS
uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- name: Install dependencies
run: yarn install
- name: Create preview
uses: expo/expo-github-action/preview@v8
with:
command: eas update --auto
```
In the above script:
- You are using the workflow event `on` to run every time a pull request is opened or updated.
- In the `update` job, the Node.js version, Expo's GitHub Action and the dependencies are set up using GitHub Action's built-in cache.
- The `eas update --auto` is run by the [preview subaction](https://github.com/expo/expo-github-action/tree/main/preview#readme). It adds a comment to the pull request with basic information about the update and a QR code to scan the update.
> Don't forget to add the `permissions` section to the job. This enables the job to add comments to the pull request.
Step 3:
You can skip this step if you have already set up `EXPO_TOKEN` in the previous section. Only one valid `EXPO_TOKEN` is required to authenticate GitHub Actions with your Expo account.
If you haven't, you need to give the script above permission to run by providing an `EXPO_TOKEN` environment variable.
- Navigate to [https://expo.dev/settings/access-tokens](https://expo.dev/settings/access-tokens).
- Click **Create token** to create a new personal access token.
- Copy the token generated.
- Navigate to https://github.com/your-username/your-repo-name/settings/secrets/actions by replacing "your-username" and "your-repo-name" with your project's info.
- Under **Repository secrets**, click **New repository secret**.
- Create a secret with the name **EXPO_TOKEN**, and paste the copied access token in as the value.
Your GitHub Action should be set up now. Whenever a developer creates a pull request, this action will build an update and publish it, making it available to all reviewers with builds that have access to the EAS branch.
> Some repositories or organizations might need to explicitly enable GitHub Workflows and allow third-party Actions.
## Using Bun instead of Yarn
To use [Bun](/guides/using-bun/) as the package manager instead of Yarn, follow the steps below for both publishing updates on push and previews on pull requests:
Step 1:
Replace the `Setup Node` step in **update.yml** or **preview.yml** with the following snippet:
```yaml update.yml/preview.yml
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
```
Step 2:
To install dependencies using Bun, replace the **Install dependencies** step with the following snippet:
```yaml update.yml/preview.yml
- name: Install dependencies
run: bun install
```
# Deployment
## Deploy updates
Learn a simple but powerful process for safely deploying updates to your users.
When you have an app with multiple binary versions in production (this is common — users do not always stay up to date with your latest store release), it's important to understand what code is running on which versions and to be able to specifically target a particular version with a hotfix.
EAS Update provides "channels", "branches", and "runtime versions" to help you determine which app version to target, to help you with bookkeeping to understand the state of your deployments, and to support a variety of [deployment patterns](/eas-update/deployment-patterns/).
Note: What if my preferred release process isn't supported by EAS Update?
---
Release management is a large topic in software engineering, and everyone has a slightly different take on how they would like to do it. EAS Update is designed to support [a variety of different workflows](/eas-update/deployment-patterns/), but this guide will focus on the simplest workflow that works for most apps. That said, there are some other workflows that may not work within the constraints of the EAS Update service. For example, each binary version must always point to a single channel, and you cannot dynamically update the channel.
As an escape hatch, you can host your own update service that is compatible with the [Expo Updates Protocol](/technical-specs/expo-updates-1/) and point your `expo-updates` configuration to that service instead. The only concepts relevant to update selection that exist on the protocol level are "Runtime Version" and "Platform", and you are free to create your own concepts on top of those in the same way we built channels and branches. [Learn more about creating a custom expo-updates server](https://github.com/expo/custom-expo-updates-server).
---
## A simple release process
In this guide, we'll describe a simple but powerful release process that uses **channels** and **runtime versions**, and mostly ignores _branches_. This gives most of the benefits of EAS Update with a minimal amount of conceptual overhead. You can evolve this process to suit your needs as they arise, or move to [other deployment patterns](/eas-update/deployment-patterns/).
Note: Why ignore branches in this release process?
---
The most simple way to use EAS Update is to ignore the concept of "branches" and focus on "channels". Branches will still exist, but you will not have to interact with them directly to manage deployments. You can keep your channels pointed at a branch with the same name as the channel and think of them as a singular concept.
EAS Update branches were meant to map to Git branches and enable teams to publish changes from a Git branch directly to an EAS Update branch of the same name. This can be helpful for [previewing updates](/eas-update/develop-faster/), but for many apps, this level of integration with Git is not required. Often, developers are only interested in being able to release hotfixes to a staging or production version of their app manually, and can run `eas update --channel staging` or `eas update --channel production`, when needed rather than managing branches to accomplish the same result.
---
## Configuring your project
Channels will indicate which environment the update targets (such as "production" or "staging"), and runtime versions will indicate the app version that the update will target (such as "1.0.0" or "1.0.1").
### Channel configuration
Run `eas update:configure` in your project if you haven't already.
**If you use EAS Build**, the default configuration that will be applied by the configure command, which is almost what we want to use here: each profile will point to a channel of the same name, so our production release of our app will point to the "production" channel. We only need to add a "staging" profile that points to the "staging" channel.
Note: Example eas.json configuration
---
The following configuration is approximately what `eas update:configure` will generate for you if you haven't already configured your project.
```json
{
"build": {
"production": {
"channel": "production"
},
"staging": {
"channel": "staging"
},
"preview": {
"channel": "preview",
"distribution": "internal"
}
}
}
```
---
**If you do not use EAS Build**, you will need to modify the channel used in your [native project configuration](/eas-update/getting-started/#configure-the-update-channel). When you release to production, ensure you update the channel name in native config to "production", and when you release to staging, ensure you update the channel name in native config to "preview". It's worth noting that using EAS Build with EAS Update helps you get the best out of the product, but it is not required.
### Runtime version configuration
By default, `eas update:configure` will set `"runtimeVersion": { "policy": "appVersion" }` in your app config. This is the recommended configuration, it will ensure that the runtime version of your app is always the same as the app version, and you have a unique runtime version to target for every release of your app. In this case, the app version refers to the native version of your app that users will see on the app store, and it does not include the build number or version code. For example: `"1.0.0"` will be used as the runtime version, and not `"1.0.0(1)"` (where `1` is the build number or version code).
Note: Example app.json configuration
---
```json
{
"expo": {
"runtimeVersion": {
"policy": "appVersion"
}
}
}
```
---
Note: What about the fingerprint runtime version policy?
---
We hope that this will be the future of runtime version policies, but for now, we recommend using the `"appVersion"` policy. The `"fingerprint"` policy is experimental and not yet widely recommended.
---
## Deploying previews
You can preview updates in your internal distribution release builds or in development builds. Using internal distribution instead of deploying to a store beta track reduces the friction of distributing the app to internal testers, and is suitable for cases where you want to, for example, share a build on every pull request or an early concept that you're working on.
### Internal distribution release builds
As explained above, preview builds will point to the "preview" channel. If you want multiple versions of the preview app distributed internally at any given time, you can change the channel name based on the feature name. For example, you might set your channel on your build to "preview-feature-a" when working on feature A, and then set it to "preview-feature-b" when working on feature B.
### Preview in development builds
Development builds can load updates from any channel, provided the runtime version is compatible. Learn more about this in [Previewing updates](/eas-update/develop-faster/).
## Deploying to staging
Run `eas update --channel staging` to publish an update to staging. This will make your hotfix immediately available to users of staging builds with the targeted runtime version.
Your staging environment will be Google Play Beta or TestFlight — the "beta track" on respective app stores. You may alternatively use internal distribution, but deploying to a store beta track is generally recommended when you are staging code for a production release since users are able to access it without any knowledge of internal processes for distributing the app (while using internal distribution would require users to download the app from an expo.dev URL).
A common practice for creating staging builds is to always create one whenever you upload a production build to a store. This allows you to have a staging build with an identical runtime to the production build, which you can use to test your updates before rolling them out to production. With EAS Build, this means running `eas build --profile staging --auto-submit` every time you run `eas build --profile production --auto-submit`.
## Deploying to production
Run `eas update --channel production` to bundle and push a new update to production. This will make your hotfix immediately available to production build users with the same runtime version.
**If you have already published the fix to staging and verified it there**, ensure that you are republishing from the same commit.
For this release process we recommend using identical environment variables and code signing configuration on staging as on production, to ensure that updates verified in staging work exactly the same in production. If do this, then you can `eas update:republish --destination-channel production` to promote the update rather than generating a new one. This will ensure the exact same bundle that you tested in staging is used in production.
Run `eas update --channel production` to publish an update to production. This will make your hotfix immediately available to users of production builds with the same runtime version.
### Runtime versions
When creating a new production build, we recommend incrementing your [app version](/build-reference/app-versions/#app-versions) to ensure it has a unique runtime version for each release of your app.
### Gradually rolling out updates
Rollouts that leverage multiple branches can't be used with this workflow. We're working on a solution for this, but in the meantime, you will need to use a process similar to the [branch promotion flow](/eas-update/deployment-patterns/#branch-promotion-flow) to incrementally roll out updates.
Learn more in [Rollouts](/eas-update/rollouts/).
### Rolling back to a previous update version
If you've mistakenly published an update to any of your environments, you can run `eas update:rollback` initiate a rollback to a previous update.
Learn more in [Rollbacks](/eas-update/rollbacks/).
## Next steps
- [Learn more about the Persistent staging release process](/eas-update/deployment-patterns/#persistent-staging-flow), which is very similar to what is described here.
- [Explore using preview updates in development builds](/eas-update/develop-faster/).
## Downloading updates
Learn strategies for downloading and launching updates.
> **info** All of the following information on this page applies only to release builds and debug builds [with `EX_UPDATES_NATIVE_DEBUG` enabled](/eas-update/debug/#runtime-issues).
In this section, we'll cover the different strategies for downloading and launching updates. The goal is to ensure that the end user adopts the latest version of the app as soon as possible after it is published, without sacrificing the user experience by introducing slow loading screens or other issues. The strategies are not mutually exclusive, and you can mix and match them as needed for the requirements of your app.
## Updates are loaded asynchronously on startup by default
The default behavior is to check for an update when the app is cold booted (launched from a killed state) and download the update if it's available. This process does not block loading the app, so when using this strategy the end user will only load the update when they cold boot the app after an update has been published, and then at some point kill and restart the app (for example, if they close it from the recent apps list on the OS or if they turn the device off and on).
This behavior is safe because it doesn't interfere with app startup to wait for a network request to complete (which would be a bad user experience in common real-world cases where a user finds themself with a slow connection and they are stuck on a loading screen for several seconds). The downside is that it takes much longer for users to adopt the latest version of the app. If the ideal is for an update to be adopted immediately by all users as soon as it is published, then this strategy falls very short of that.
Note: What if I want to always block app startup until the latest update is downloaded?
---
We recommend against this strategy because the resulting user experience is extremely poor. Typically when a user is stuck waiting on a splash screen when booting an app, they will close the app and try again (and so downloading the update won't complete), or give up and use another app. When the users' device is connected to a slow network, even when there is no update, they may have to wait several seconds or more to load the app. If ensuring that your users always have the latest version of your app is critical, you may want to explore one of the other strategies explained here.
---
Note: How can I disable the default behavior?
---
You can disable the default behavior by setting the [`checkAutomatically`](/versions/latest/sdk/updates/#updatescheckautomaticallyvalue) option to `NEVER` in the `updates` configuration. This will prevent the app from checking for updates and downloading them automatically.
---
## Checking for updates while the app is running
You can use `Updates.checkForUpdateAsync()` to check for updates while the app is running. This will return a promise that resolves to a [`UpdateCheckResult` object](/versions/latest/sdk/updates/#updatecheckresult), with `isAvailable` set to `true` if an update is available, and information about the update in the [`manifest`](/versions/latest/sdk/manifests/#expoupdatesmanifest) property.
If an update is available, you can use the `Updates.downloadAsync()` method to download the update. This will return a promise that resolves when the download is complete. Finally, you can use the `Updates.reloadAsync()` method to reload the app with the new version. The `useUpdates()` hook can also be used to monitor the state of the `expo-updates` library from a React component.
Note: What are common patterns for checking for updates while the app is running?
---
- You can check for updates at various points in your app's lifecycle, such as [when it is foregrounded](https://reactnative.dev/docs/appstate) or at some interval. When an update is found, you may want to show a dialog to the user to prompt the user to update.
- You can check for updates at launch and display your own custom loading screen, if it is very important for your use case to ensure that users always get the latest version at launch.
---
## Checking for updates while the app is backgrounded
You can use [`expo-background-task`](/versions/latest/sdk/background-task/) to check for updates while the app is backgrounded. To do this, use the same `Updates.checkForUpdateAsync()` and `Updates.downloadAsync()` methods as you would in the foreground, but execute them inside of a background task. This is a great way to ensure that the user always has the latest version of the app, even if they have not opened the app in a while.
It's worth considering whether you want to reload the app after an update is downloaded in the background, or wait for the user to close and reopen it. If you choose to only download it in the background and not apply it, this should still be useful to ensure that the next boot will immediately have the latest version, and it will lead to a faster adoption rate for updates compared to the default behavior.
Note: An example of how to check for updates in the background
---
To ensure the background task is registered when the application starts, import and invoke the `setupBackgroundUpdates` function within the top-level component.
```ts
import * as TaskManager from 'expo-task-manager';
import * as BackgroundTask from 'expo-background-task';
import * as Updates from 'expo-updates';
export const setupBackgroundUpdates = async () => {
const BACKGROUND_TASK_NAME = 'task-run-expo-update';
// Define the background task - this must be done each time the app starts
TaskManager.defineTask(BACKGROUND_TASK_NAME, async () => {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
return Promise.resolve();
});
// Register the task if it was not previously registered
const isRegistered = TaskManager.isTaskRegisteredAsync(BACKGROUND_TASK_NAME);
if (!isRegistered) {
await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_NAME, {
minimumInterval: 60 * 24, // 24 hours
});
}
};
```
---
Note: Should I also apply the update with Updates.reloadAsync() when the app is backgrounded?
---
**Support for calling `Updates.reloadAsync()` while the app is backgrounded is experimental**. This is a new feature and it is not widely used, be sure to monitor for crashes when you first enable it. Downloading an update in the background is safe.
Reloading an update while the app is backgrounded can be a great way to ensure that the user has the latest version of the app when they open it again. However, it is important to note that, unless you persist the state that the app was in at the time it was backgrounded and restore that state, the user will experience a cold boot when they open the app again. One way to mitigate this is to only do a reload in the background if the app has been inactive for a certain period of time, after which a user is unlikely to expect the app to restore its previous state.
---
## Critical/mandatory updates
There is no first-class support for critical/mandatory updates in the `expo-updates` library. However, you can implement your own logic to check for critical updates and apply them manually. The [`expo/UpdatesAPIDemo` repository](https://github.com/expo/UpdatesAPIDemo) contains an example of one way to approach this. You can combine that approach with the strategies above to check for updates.
## Controlling which update to load from the client side
The typical way to use EAS Update is to have a single update URL and a set of request headers (such as update channel name) embedded in a build of your app. To control which update is loaded, you make changes on the server through the `eas update` command or the EAS dashboard. For example, you publish a new update to a channel that your build is pointing to, then the build fetches that update on the next launch. Updates published to a channel different from the one your build is pointing to will not be downloaded with this approach.
You can override the update URL and request headers at runtime using the `Updates.setUpdateURLAndRequestHeadersOverride()` method. This can be useful if you want to load a specific update or change the update channel while the app is running. [Learn more](/eas-update/override/).
## Monitoring adoption of updates
The details page for an update (for example: `https://expo.dev/accounts/[account]/projects/[project]/updates/[id]`) shows metrics for the number of users who have run the update, in addition to the number of failed installs (users who download and attempted to run the update, but it crashed).
The deployments page (for example: `https://expo.dev/accounts/[account]/projects/[project]/deployments/production/[runtime-version]`) includes a table and chart that shows the number of users who have run each update related to a particular update channel and runtime version combination, over a given time period.
## Rollouts
Learn how to incrementally deploy updates to your users by using a rollout mechanism.
A rollout allows you to roll out a change to a portion of your users to catch bugs or other issues before releasing that change to all your users.
EAS provides **per-update and branch-based rollout mechanisms** depending on your use case.
## Per-update rollouts
This rollout mechanism allows you to specify a percentage of users that should receive a new update when you publish it, and then increase that percentage gradually afterwards.
### Starting a rollout
To start an update-based rollout, add the `--rollout-percentage` flag to your normal `eas update` command:
```sh
$ eas update --rollout-percentage=10
```
In this example, when published, the update will only be available to 10% of your end users.
### Progressing a rollout
To edit the percentage of an update-based rollout:
```sh
$ eas update:edit
```
You will be guided through the process of selecting the update to edit and asked for the new percentage.
### Ending a rollout
When ending an update-based rollout, you have two options:
- **Roll out fully**: To accomplish this end state, progress the rollout as detailed above and set the percentage to 100.
- **Revert**: To accomplish this end state, republish the previous update by using the `eas update:republish` command.
### Working with rollouts
- Only one update can be rolled out on a branch at one time.
- When a rollout is in progress, it must be ended using one of the options above before a new update (with the same runtime version) can be published. This prevents accidentally clobbering the rollout.
- To see the state of the rollout, use the `eas update:list` or `eas update:view` commands.
## Branch-based rollouts
This rollout mechanism allows you to incrementally roll out a set of updates on a new branch to a percentage of end users and leave the remaining percentage of users on the current branch.
### Starting a rollout
To start a branch-based rollout, run the following EAS CLI command:
```sh
$ eas channel:rollout
```
In the terminal, an interactive guide will assist you in selecting a channel, choosing a branch for the rollout, and setting the percentage of users for the rollout. To increase or decrease the rollout amount, run the command again and choose the `Edit` option to adjust the rollout percentage.
### Ending a rollout
Two methods are available to end a rollout when you choose the `End` option in the interactive guide:
- **Republish and revert:** Use this option when you are confident with the state of the new branch. This will republish the latest update from the new branch to the old branch, and all users will be pointed to the old branch.
- **Revert:** Choose to disregard the updates on the new branch and return users to the old branch.
### Working with rollouts
- Only one branch can be rolled out on a channel at a single time.
- To see the state of the rollout, use the `eas channel:rollout` command.
- When a rollout is in progress, you can publish updates to both rolled out and current branches by running `eas update --branch [branch]`, for example.
- `eas update --channel [channel]` cannot be used when a rollout is in progress since it cannot know which branch in the rollout to associate the update with.
## Rollbacks
Rollback a branch to a previous update or the embedded update.
There are two types of rollbacks supported by EAS Update:
- Roll back to a previously-published update.
- Roll back to the update embedded in the build.
## Start a rollback
To start a rollback, run the following command:
```sh
$ eas update:rollback
```
In the terminal, an interactive guide will assist you in selecting the type of rollback and doing the rollback.
## Rolling back to a previously-published update
The above command re-publishes a previously-published update to functionally roll back clients to that update.
## Rolling back to the update embedded in the build
The above command instructs the client to run the update embedded in the build.
## Publishing after the rollback
Upon publishing again after a rollback, all clients will receive the new update.
## Optimize assets for EAS Update
Learn how EAS Update downloads assets and how to optimize them for download size.
> **info** For SDK 50 and above, the new [asset selection feature](/eas-update/asset-selection) can greatly reduce the total number and size of downloaded assets.
When an app finds a new update, it downloads a manifest and then downloads any new or updated assets so that it can run the update. The process is as follows:
Many users running Android and iOS apps are using mobile connections that are not as consistent or fast as when they are using Wi-Fi, so it's important that the assets shipped as a part of an update are as small as possible.
## Code assets
When publishing an update, EAS CLI runs Expo CLI to bundle the project into an update. The update will appear in our project's **./dist** directory.
In **./dist/bundles**, we can see the size of the **index.android.js** and **index.ios.js** files that will be part of the Android and iOS updates, respectively. Note that these are uncompressed file sizes; EAS Update uses Brotli and gzip compression, which can significantly reduce download sizes. Nevertheless, these files will be downloaded to a user's device when getting the new update if the device has not downloaded them before. Making these file sizes as small as possible helps end-users download updates quickly.
## Image assets
App users will have to download any new images or other assets when they detect a new update if those assets are not already a part of their build. You can view all the assets uploaded to EAS servers in **./dist/assets**. The assets there are hashed with their extensions removed, so it is difficult to know what assets are there. To see a pretty-printed list of assets, we can run:
```sh
$ npx expo export
```
### Optimizing image assets
To manually optimize image assets in your project, you can use the `npx expo-optimize` command. It uses [sharp](https://sharp.pixelplumbing.com/) library to compress images.
```sh
$ npx expo-optimize
```
After running the command, all image assets are compressed except the ones that are already optimized. You can adjust the compression quality by including the `--quality [number]` option with the command. For example, to compress to 90%, run:
```sh
$ npx expo-optimize --quality 90
```
### Other manual optimization methods
To optimize images and videos manually, see [Assets](/develop/user-interface/assets/#manual-optimization-methods) for more information.
## Ensuring assets are included in updates
When you publish an update, EAS will upload your assets to the CDN so that they may be fetched when users run your app. However, for assets to be uploaded to the CDN, they must be explicitly required somewhere in your application's code. Conditionally requiring assets will result in the bundler being unable to detect them, and they will not be uploaded when you publish your project.
## Further considerations
It's important to note that a user's app will only download new or updated assets. It will not re-download unchanged assets that already exist inside the app.
One way to make sure that updates stay as slim as possible is to build and submit the app frequently to the app stores so that users can download a new app binary that includes more up-to-date assets. Generally, it's a good practice to build and submit an app when adding large or multiple assets, and it's good to use updates to fix small bugs and make minor changes between app store releases.
## Continuous deployment
Learn how to use the fingerprint runtime version and GitHub actions for continuous deployment.
## Prerequisites
- You need to have a GitHub account and use it to host your git project.
- You need to have an Expo token configured in your GitHub repository.
Note: How to configure the environment variable on your GitHub repository
---
- Navigate to [expo.dev/settings/access-tokens](https://expo.dev/settings/access-tokens) and then:
- Click **Create token** to create a new personal access token.
- Copy the generated token.
- Navigate to https://github.com/your-username/your-repo-name/settings/secrets/actions by replacing "your-username" and "your-repo-name" with your project's info.
- Under **Repository secrets**, click **New repository secret**.
- Create a secret with the name **EXPO_TOKEN**, and paste the copied access token in as the value.
---
## GitHub Action: continuous-deploy-fingerprint
> **info** Available for SDK 52 and above.
Expo provides the [continuous-deploy-fingerprint](https://github.com/expo/expo-github-action/tree/main/continuous-deploy-fingerprint) GitHub Action to continuously deploy React Native projects that use the [`fingerprint` runtime version policy](/versions/latest/sdk/updates/#fingerprint). It deploys every JS change as an EAS Update to all compatible builds over-the-air, and new builds are automatically created using EAS Build when a runtime change is detected.
Note: How do I debug the commands that are run in the action?
---
Running your GitHub workflows in debug mode will automatically add the `--debug` flag to the commands run as part of the action, and the output will be available in the workflow run logs.
---
### Fingerprint runtime versioning
The [`fingerprint` runtime version policy](/versions/latest/sdk/updates/#fingerprint) uses the [`@expo/fingerprint`](/versions/latest/sdk/fingerprint) package to generate a hash of your project when making a build or publishing an update, and then uses that hash as the runtime version. The hash is calculated based on dependencies, custom native code, native project files, and configuration, amongst other things.
By automatically calculating the runtime version, you don't have to be concerned about native layer compatibility with the JavaScript application when deploying updates to builds.
Note: How does fingerprint generation differ between managed and bare workflows?
---
The default project files included in the fingerprint hash differs between managed and bare workflow projects. EAS automatically detects your workflow by checking if the **android** and **ios** and directories are gitignored. If they are, EAS will treat the project as a managed workflow project, thus dictating that a hash of the `package.json` dependencies are sufficient to determine fingerprint compatibility.
---
Note: How do I debug fingerprint mismatches between my local machine and CI/CD?
---
If you notice different fingerprints being generated across different machines or environments, it may mean that unanticipated files are being included in the hash calculation. `@expo/fingerprint` has a predetermined set of files to include/exclude for hash calculation, but often your project setup may require additional excludes. For projects that include native directories (**android** and **ios**) this is more common.
We provide tools for identifying which files are causing fingerprint inconsistencies and mechanisms to exclude those files from fingerprint calculations for your project.
To identify differences in fingerprints on different machines or environments:
- When running fingerprint generation commands on each machine/environment (`npx expo-updates fingerprint:generate`), pass `--debug` flag.
- Diff outputs from those command runs to determine files causing the difference. These tools may be helpful:
- [JSON Pretty Print](https://jsonformatter.org/json-pretty-print) to format the output.
- [JSON Diff](https://www.jsondiff.com/) to compare the output and identify the files causing the discrepancies.
To exclude files causing the differences, add them to the **.fingerprintignore** file as described in the documentation for [`@expo/fingerprint`](/versions/latest/sdk/fingerprint).
---
### App version numbers
When using continuous deployment, version number incrementation must be done automatically. While the specifics will differ based on the preferred numbering scheme, the general strategy is:
1. Before continuous deployment CI steps, check if a new release will be created (due to a new fingerprint).
2. If so, bump the version number in the source code and push the version bump commit.
3. Continue continuous deployment CI steps with the new commit.
An example is provided in the [`continuous-deploy-fingerprint` GitHub Action documentation](https://github.com/expo/expo-github-action/tree/main/continuous-deploy-fingerprint#continuous-deployment-on-main-advanced).
## GitHub Action: expo-github-action
An alternative GitHub action is [expo-github-action](https://github.com/expo/expo-github-action/tree/main) that can be used to publish updates on push and previews on pull requests. `continuous-deploy-fingerprint` uses fingerprints to determine if a build is needed or not to deploy an update, while `expo-github-action` will only publish updates.
- To learn about publishing updates when code is pushed to the main branch, refer to the [README](https://github.com/expo/expo-github-action/tree/main?tab=readme-ov-file#create-new-eas-update-on-push-to-main).
- To learn about Pull Request previews with `expo-github-action`, refer to the [GitHub PR Previews guide](/eas-update/github-actions/).
## Alternative deployment patterns
Learn about different deployment patterns for your project when using EAS Update.
Once we've created features and fixed bugs in our app, we want to deliver those features and bug fixes to our users as quickly and safely as we can. Often "safe" and "fast" are opposing forces when delivering code to our users.
We could push our code directly to production, which would be fast yet less safe since we never tested our code. On the other hand, we could make test builds, share them with a QA team, and release them periodically, which would be safer but slower to deliver changes to our users.
Depending on your project, you'll have some tolerance for how "fast" and how "safe" you'll need to be when delivering updates to your users.
There are three parts to consider when designing the EAS Update deployment process:
1. **Creating builds**
- (a) We can create builds for production use only.
- (b) We can create builds for production use and separate builds for pre-production change testing.
2. **Testing changes**
- (a) We can test changes with TestFlight and Play Store Internal Track.
- (b) We can test changes with an internal distribution build.
- (c) We can test changes with Expo Go or a [development build](/develop/development-builds/introduction/).
3. **Publishing updates**
- (a) We can publish updates to a single branch.
- (b) We can create update branches that are environment-based, like "production" and "staging".
- (c) We can create update branches that are version-based, like "version-1.0", which enables us to promote updates from one channel to another.
We can mix, match, and tweak the parts above to create a process that is the right balance of release cadence and safety for our team and users.
Another trade-off to consider is the amount of bookkeeping of versions/names/environments we'll have to do throughout the process. The less bookkeeping we have to do will make it easier to follow a consistent process. It'll also make it easier to communicate with our colleagues. If we need fine-grained control, bookkeeping will be required to get the exact process we want.
Below, we've outlined four common patterns on how to deploy a project using EAS Update.
## Two-command flow
This flow is the simplest and fastest flow, with the fewest amount of safety checks. It's great for trying out Expo and for smaller projects. Here are the parts of the deployment process above that make up this flow:
**Creating builds:** (a) Create builds for production use only.
**Testing changes:** (c) Test changes with Expo Go or a [development build](/develop/development-builds/introduction/).
**Publishing updates:** (a) Publish to a single branch.
### Diagram of flow
### Explanation of flow
1. Develop a project locally and test changes in a development build or in Expo Go.
2. Run `eas build` to create builds, then submit them to app stores. These builds are for public use and should be submitted/reviewed, and released on the app stores.
3. When we have updates we'd like to deliver, run `eas update --branch production` to deliver updates to our users immediately.
#### Advantages of this flow
- This flow does not require bookkeeping extra version or environment names, which makes it easy to communicate to others.
- Delivering updates to builds is very fast.
#### Disadvantages of this flow
- There are no pre-production checks to make sure the code will function as intended. We can test with Expo Go or a [development build](/develop/development-builds/introduction/), but this is less safe than having a dedicated test environment.
## Persistent staging flow
This flow is like an un-versioned variant of the "branch promotion flow". We do not track release versions with branches. Instead, we'll have persistent "staging" and "production" branches that we can merge into forever. Here are the parts of the deployment process above that make up this flow:
**Creating builds:** (b) Create builds for production and separate builds for testing.
**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds.
**Publishing updates:** (b) Create update branches that are environment-based, like "staging" and "production".
### Diagram of flow
### Explanation of flow
1. Develop a project locally and test changes in Expo Go.
2. Create builds with channels named "production", which will eventually get reviewed and become available on app stores. Create another set of builds with channels named "staging", which will be used for testing on TestFlight and the Play Store Internal Track.
3. Set up `expo-github-action` to publish updates when merging commits to branches.
4. Merge changes into a branch named "staging". The GitHub Action will publish an update and make it available on our test builds.
5. When ready, merge changes into the "production" branch to publish an update to our production builds.
#### Advantages of this flow
- This flow allows you to control the pace of deploying to production independent of the pace of development. This adds an extra chance to test your app and avoids your user having to download a new update every time a PR is landed.
- It's easy to communicate to your team, since deploying updates occurs when merging into GitHub branches named "staging" and "production".
#### Disadvantages of this flow
- Checking out previous versions of your app is slightly more complex, since we'd need to check out an old commit instead of an old branch.
- When merging to "production", the update would be re-built and re-published instead of moved from the builds with channel "staging" to the builds with channel "production".
## Platform-specific flow
This flow is for projects that need to build and update their Android and iOS apps separately all the time. It will result in separate commands for delivering updates to the Android and iOS apps. Here are the parts of the deployment process above that make up this flow:
**Creating builds:** (a) Create builds for production only, or (b) create builds for production and separate builds for testing.
**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds.
**Publishing updates:** (b) Create update branches that are environment- and platform-based, like "ios-staging", "ios-production", "android-staging", and "android-production".
### Diagram of flow
### Explanation of flow
1. Develop a project locally and test changes in Expo Go.
2. Create builds with channels named like "ios-staging", "ios-production", "android-staging", and "android-production". Then put the "ios-staging" build on TestFlight and submit the "ios-production" build to the public App Store. Likewise, put the "android-staging" build on the Play Store Internal Track, and submit the "android-production" build to the public Play Store.
3. Set up `expo-github-action` to publish updates to the required platforms when merging commits to branches.
4. Then, merge changes for the iOS app into the branch "ios-staging", then when ready merge changes into the "ios-production" branch. Likewise, merge changes for the Android app into the branch "android-staging" and when ready, into the branch named "android-production".
#### Advantages of this flow
- This flow gives you full control of which updates go to your Android and iOS builds. Updates will never apply to both platforms.
#### Disadvantages of this flow
- You'll have to run two commands instead of one to fix changes on both platforms.
## Branch promotion flow
This flow is an example of a flow for managing versioned releases.
> **warning** This flow requires a bit more bookkeeping and does not support automatic [runtime version policies](/eas-update/runtime-versions/#setting-runtimeversion) (`"sdkVersion"`, `"appVersion"`, `"nativeVersion"` and `"fingerprint"`). You will need to [manually specify](/eas-update/runtime-versions/#custom-runtimeversion) your runtimes' versions with this flow.
Here are the parts of the deployment process above that make up this flow:
**Creating builds:** (b) Create builds for production (one per major version) and separate builds for testing.
**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds.
**Publishing updates:** (c) Create update branches that are version based, like "version-1.0". Branches are dynamically mapped to channels to promote well-tested changes from testing to production.
### Diagram of flow
### Explanation of flow
1. Develop a project locally and test changes in Expo Go or a [development build](/develop/development-builds/introduction/).
2. Create builds with channels named "production-rtv-1" (indicating a channel with a runtime version "1"), which will eventually get reviewed and become available on app stores. Create another set of builds with channels named "staging", which will be used for testing on TestFlight and the Play Store Internal Track.
3. Set up `expo-github-action` to publish updates when merging commits to branches.
4. Merge changes into a branch named "version-1".
5. Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-1". Test the update by opening the apps on TestFlight and the Play Store Internal Track.
6. When ready, use the website or EAS CLI to point the "production-rtv-1" channel at the EAS Update branch "version-1".
7. Then, there are two update scenarios you may encounter:
- A new release does not require a new runtime version:
1. Create another GitHub branch named "version-2".
2. Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-2".
3. Merge commits into the "version-2" branch until the new features and fixes are ready and stable.
4. Use the website or EAS CLI to point the "production-rtv-1" channel at the EAS Update branch "version-2". This will mean that everyone with a production build (users who downloaded the app from the app stores) will now get the latest update on the "version-2" branch.
- A new release requires a new runtime version (for example, when new native libraries are added or SDK version is upgraded):
1. Bump your runtime version from "1" to "2".
2. Create a new "staging" build with the new runtime version.
3. Create another GitHub branch named "version-2".
4. Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-2".
5. Merge commits into the "version-2" branch until the new features and fixes are ready and stable.
6. Create a new build with channel named "production-rtv-2", which will eventually get reviewed and become available on app stores.
7. Use the website or EAS CLI to point the "production-rtv-2" channel at the EAS Update branch "version-2". This will mean that everyone who previously had a production build (users who downloaded the app from the app stores) will continue to get the latest update on EAS Update branch "version-1" until they download the new version of the app from the app store, at which time they will get the latest update on EAS Update branch "version-2".
#### Advantages of this flow
- This flow is safer than the other flows. All updates are tested on test builds which are distributed to internal testers, and branches are moved between channels, so the exact artifact tested is the one deployed to production builds.
- This flow creates a direct mapping between GitHub branches and EAS Update branches. It also creates a mapping between GitHub commits and EAS Update updates. If you're keeping track of GitHub branches, you can create EAS Update branches for each GitHub branch and link those branches to a build's channel.
In practice, this makes it so you can push to GitHub, then select the same branch name on Expo to link to builds.
- Previous versions of your deployments are always preserved on GitHub. Once the "version-1.0" branch is deployed, then another version is deployed after it (like "version-1.1"), the "version-1.0" branch is forever preserved, making it easy to checkout a previous version of your project.
#### Disadvantages of this flow
- One channel per production runtime version is needed to maintain historical updates for previous production releases. This makes using a runtime version policy more difficult.
- Bookkeeping of branch names is required for this flow, which will mean communicating with your team which branches are currently pointed at your test builds and your production builds.
# Concepts
## How EAS Update works
A conceptual overview of how EAS Update works.
EAS Update is a service that allows you to deliver small bug fixes and updates to your users immediately as you work on your next app store release. Making an update available to builds involves creating a link between a build and an update.
To create a link between a build and an update, we have to make sure the update can run on the build. We also want to make sure we can create a deployment process so that we can expose certain updates to certain builds when we're ready.
To illustrate how builds and updates interact, take a look at the following diagram:
Builds can be thought of as two layers: a native layer that's built into the app's binary, and an update layer, that is swappable with other compatible updates. This separation allows us to ship bug fixes to builds as long as the update with the bug fix can run on the native layer inside the build.
To make sure the update can run on the build, we have to set a variety of properties so that we can be sure our builds can run our updates. This starts when we create a build of our project.
## Conceptual overview
### Distributing builds
When we're ready to create a build of our Expo project, we can run `eas build` to create a build. During the build, the process will include some properties inside the build that are important for updates. They are:
- **Channel:** The channel is a name we can give to multiple builds to identify them easily. It is defined in `eas.json`. For instance, we may have an Android and an iOS build with a channel named "production", while we have another pair of builds with a channel named "staging". Then, we can distribute the builds with the "production" channel to the public app stores, while keeping the "staging" builds on the Play Store Internal Track and TestFlight.
Later when we publish an update, we can make it available to the builds with the "staging" channel first; then once we test our changes, we can make the update available to the builds with the "production" channel.
- **Runtime version:** The runtime version describes the JS-native interface defined by the native code layer that runs our app's update layer. It is defined in a project's [app config](/workflow/configuration/). Whenever we make changes to our native code that change our app's JS-native interface, we'll need to update the runtime version. [Learn more.](/eas-update/runtime-versions)
- **Platform:** Every build has a platform, such as "Android" or "iOS".
If we made two sets of builds with the channels named "staging" and "production", we could distribute builds to four different places:
This diagram is just an example of how you could create builds and name their channels, and where you could put those builds. Ultimately it's up to you which channel names you set and where you put those builds.
### Publishing an update
Once we've created builds, we can change the update layer of our project by publishing an update. For example, we could change some text inside **App.js**, then we could publish that change as an update.
To publish an update, we can run `eas update --auto`. This command will create a local update bundle inside the **dist** directory in our project. Once it's created an update bundle, it will upload that bundle to EAS servers, in a database object named a _branch_. A branch has a name and contains a list of updates, where the most recent update is the active update on the branch. We can think of EAS branches just like Git branches. Just as Git branches contain a list of commits, EAS branches contain a list of updates.
### Matching updates and builds
Like builds, every update on a branch includes a target runtime version and target platform. With these fields, we can make sure that an update will run on a build with something called an _update policy_. EAS' update policy is as follows:
- The platform of the build and the target platform of an update must match exactly.
- The runtime version of the build and the target runtime version of an update must match exactly.
- A channel can be linked to any branch. By default, a channel is linked to a branch of the same name.
Let's focus on that last point. Every build has a channel, and we, as developers, can link that channel to any branch, which will make its most recent compatible update available on the branch to the linked channel.
To simplify this linking, by default we auto-link channels to branches of the same name. For instance, if we created builds with the channel named "production", we could publish updates to a branch named "production" and our builds would get the updates from the branch named "production", even though we did not manually link anything.
This default linking works great if you have a deployment process where you have multiple consistent Git and EAS branches.
For instance, we could have a "production" branch and a "staging" branch, both on Git and on EAS. Paired with a [GitHub Action](/eas-update/github-actions), we could make it so that every time a commit is pushed to the "staging" Git branch, we publish to the "staging" EAS Update branch, which would make that update apply to all our builds with the "staging" channel.
Once we tested changes on the staging builds, then we could merge the "staging" Git branch into the "production" Git branch, which would publish an update on the "production" EAS Update branch. Finally, the latest update on the "production" EAS Update branch would apply to builds with the "production" channel.
This flow makes it so that we can push to GitHub, then see our builds update without any other interventions.
While this flow works for many developers, there's another flow we can accomplish since we have the ability to change the link between channels and branches. Imagine we name our branches like "version-1.0", "version-2.0", and "version-3.0". We could link the "version-1.0" EAS Update branch to the "production" channel, to make it available to our "production" builds.
We could also link the "version-2.0" EAS Update branch to the "staging" channel to make it available to testers. Finally, we could make a "version-3.0" EAS Update branch that is not linked to any builds yet, that only developers are testing with a development build.
Once testers verify that the update on the "version-2.0" EAS Update branch is ready for production, we can update the "production" channel so that it's linked to the "version-2.0" branch. To accomplish this, we could run:
```sh
$ eas channel:edit production --branch version-2.0
```
After this state, we'd be ready to start testing the "version-3.0" EAS Update branch. Similarly to the last step, we could link the "staging" channel to the "version-3.0" EAS Update branch with this command:
```sh
$ eas channel:edit staging --branch version-3.0
```
## Practical overview
Now that we're familiar with the core concepts of EAS Update, let's talk about how this process occurs.
When an Expo project that includes `expo-updates` is built the included native Android and iOS code is responsible for managing, fetching, parsing, and validating updates.
When the library checks for updates and when it downloads them, they are [configurable](/versions/latest/config/app/#updates). By default, the library will check for an update when it is opened. If an update newer than the currently running update is found, it will download and run the newer update. If the library does not find a newer update, it will instead run the newest downloaded update, falling back to the update that was embedded inside the app at build time if none have been downloaded.
`expo-updates` downloads updates in two phases. First, it downloads the most recent update _manifest_, which contains information about the update including a list of assets (images, JavaScript bundles, font files, and so on) that are required to run the update.
Second, the library downloads the assets specified in the manifest that it has not yet downloaded from prior updates. For instance, if an update contains a new image, the library will download the new image asset before running the update. To help end-users get updates quickly and reliably, updates should be kept as small as possible.
If the library is able to download the manifest (phase 1) and all the required assets (phase 2) before the `fallbackToCacheTimeout` setting, then the new update will run immediately upon launch. If the library is not able to fetch the manifest and assets within `fallbackToCacheTimeout`, it will continue to download the new update in the background and will run it upon the next launch.
## Wrap up
With EAS Update, we can quickly deliver small, critical bug fixes to our users and give users the best experience possible. This is set up with a build's runtime version, platform, and channel. With these three constraints, we can make an update available to a specific group of builds. This allows us to test our changes before going to production within a deployment process.
Depending on how we set up our deployment process, we can optimize for speed. We can also optimize our deployments to be as safe and bug-free as possible. The deployment possibilities are vast and can match nearly any release process you prefer.
## Manage branches and channels with EAS CLI
Learn how to link a branch to a channel and publish updates with EAS CLI.
EAS Update works by linking _branches_ to _channels_. Channels are specified at build time and exist inside a build's native code. Branches are an ordered list of updates, similar to a Git branch, which is an ordered list of commits. With EAS Update, we can link any channel to any branch, allowing us to make different updates available to different builds.
The diagram above visualizes this link. Here, we have the builds with the "production" channel linked to the branch named "version-1.0". When we're ready, we can adjust the channel–branch pointer. Imagine we have more fixes tested and ready on a branch named "version-2.0". We could update this link to make the "version-2.0" branch available to all builds with the "production" channel.
## Inspecting the state of your project's updates
### Inspect channels
View all channels:
```sh
$ eas channel:list
```
View a specific channel:
```sh
$ eas channel:view [channel-name]
$ eas channel:view production
```
Create a channel:
```sh
$ eas channel:create [channel-name]
$ eas channel:create production
```
### Inspect branches
See all branches:
```sh
$ eas branch:list
```
See a specific branch and a list of its updates:
```sh
$ eas branch:view [branch-name]
$ eas branch:view version-1.0
```
### Inspect updates
View a specific update:
```sh
$ eas update:view [update-group-id]
$ eas update:view dbfd479f-d981-44ce-8774-f2fbcc386aa
```
## Changing the state of your project's updates
### Create a new update and publish it
```sh
$ eas update --branch [branch-name] --message "..."
$ eas update --branch version-1.0 --message "Fixes typo"
```
If you're using Git, we can use the `--auto` flag to auto-fill the branch name and the message. This flag will use the current Git branch as the branch name and the latest Git commit message as the message.
```sh
$ eas update --auto
```
### Delete a branch
```sh
$ eas branch:delete [branch-name]
$ eas branch:delete version-1.0
```
### Rename a branch
Renaming branches do not disconnect any channel–branch links. If you had a channel named "production" linked to a branch named "version-1.0", and then you renamed the branch named "version-1.0" to "version-1.0-new", the "production" channel would be linked to the now-renamed branch "version-1.0-new".
```sh
$ eas branch:rename --from [branch-name] --to [branch-name]
$ eas branch:rename --from version-1.0 --to version-1.0-new
```
### Republish a previous update within a branch
We can make a previous update immediately available to all users. This command takes the previous update and publishes it again so that it becomes the most current update on the branch. As your users re-open their apps, the apps will see the newly re-published update and will download it.
> Republish is similar to a Git reversion, where the correct commit is placed on top of the Git history.
```sh
$ eas update:republish --group [update-group-id]
$ eas update:republish --branch [branch-name]
$ eas update:republish --group dbfd479f-d981-44ce-8774-f2fbcc386aa
$ eas update:republish --branch version-1.0
```
> If you don't know the exact update group ID, you can use the `--branch` flag. This shows a list of the recent updates on the branch and allows you to select the update group to republish.
## Runtime versions and updates
Learn about different runtime version policies and how they may suit your project.
Runtime versions are a property that guarantees compatibility between a build's native code and an update. When a project is made into a build, the build will contain some native code that cannot be changed with an update. Therefore, an update must be compatible with a build's native code to run on the build.
To illustrate how builds and updates interact, take a look at the following diagram:
Builds can be thought of as two layers: a native layer that's built into the app's binary, and an update layer, that is swappable with other compatible updates. This separation allows us to ship bug fixes to builds as long as the update with the bug fix can run on the native layer inside the build. The `"runtimeVersion"` property allows us to guarantee that an update is compatible with a specific build's native code.
Since updates must be compatible with a build's native code, any time native code is updated, we're required to make a new build before publishing an update. Some developers only update native code when upgrading to a new Expo SDK, while others may upgrade native code between builds or at other intervals. Below is an explanation of different situations and configurations that may suite your project.
## Setting `"runtimeVersion"`
To make managing the `"runtimeVersion"` property easier between builds and updates, we've created policies that derive the runtime version from another piece of information already present in your project. If these policies do not match the development flow of a project, there's also an option to set the `"runtimeVersion"` manually.
### Runtime version policies
The available policies are documented in the [`expo-updates` library documentation](/versions/latest/sdk/updates/#automatic-configuration-using-runtime-version-policies).
### Custom runtime version
You can also set a custom runtime version that meets the [runtime version formatting requirements](/versions/latest/config/app/#runtimeversion):
```json
{
"expo": {
"runtimeVersion": "1.0.0"
}
}
```
This option is good for developers who want to manage the runtime version manually, separately from any other version numbers present in a project's app config. It gives the developer complete control over which updates are compatible with which builds.
### Platform-specific runtime version
You can also set runtime version per-platform, for example
```json
{
"expo": {
"android": {
"runtimeVersion": "1.0.0"
}
}
}
```
Or:
```json
{
"expo": {
"android": {
"runtimeVersion": {
"policy": "appVersion"
}
}
}
}
```
When both a top level runtime and a platform specific runtime are set, the platform specific one takes precedence.
## Avoiding crashes with incompatible updates
The main issue that can arise when publishing updates is that the update could rely on native code that the build it's running on does not support. For instance, imagine we made a build with a runtime version of `"1.0.0"`. Then, we submitted that build to the app stores and released it to the public.
Later on, imagine that we developed an update that relied on a newly installed native library, like the `expo-in-app-purchases` library, and we did not update the `"runtimeVersion"` property, so that it is still `"1.0.0"`. If we published an update, the builds with the `"runtimeVersion"` of `"1.0.0"` would think the incoming update with the same runtime version was compatible and it would attempt to load the update. Since the update would make calls to code that does not exist inside the build, the app would crash.
There are a few ways to avoid crashes like this:
- Whenever we install or update native code, iterate the `"runtimeVersion"` property in the project's app config (**app.json**/**app.config.js**).
- Create a preview build of your app and load it on a test device. Then, publish the update to the "preview" branch to make sure it works as expected before publishing it to the project's production branch.
If this error does occur, then you can republish a previous known-good update, then ask users to delete the app and reinstall it.
In the future, the `expo-updates` library will prevent many instances of this from crashing the app. If we can detect this particular issue, we'll automatically roll back to a previous update instead of loading the bad update.
# Troubleshooting
## EAS Update Debugging
Learn how to use basic debugging techniques to fix an update issue.
This guide shows how to verify our configuration so that we can find the source of problems like an app not showing a published update. It's important to tell the current state of our app at any given time, so EAS Update was built 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.
Video Tutorial: [How to debug EAS Update](https://www.youtube.com/watch?v=m9PLTr3t3S4)
> If we are not using EAS Build, our Deployments page will be empty. Follow the guide on [debugging configuration without EAS Build](/eas-update/debug-advanced/#configuration-without-eas-build) instead.
## Go to the Deployments page
The EAS website has a [Deployments page](https://expo.dev/accounts/[account]/projects/[project]/deployments) that shows the current state of our app. The term _deployment_ refers to a group of builds and their corresponding updates. If we've made builds and updates with EAS, we can see our project's status on the website in the Deployments tab.
## Common problems
The following section describes common problems and how to fix them. 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 following sections, we'll inspect and verify these spots and more.
### Unexpected channel
If the deployment channel is unexpected, it means our build was not built with the correct channel. To fix this, [configure our channel](/#configure-channel) and rebuild our app.
### Unexpected runtime version
If the deployment runtime version is unexpected, it means our build was not built with the correct runtime version. To fix this, [configure our runtime version](/#configure-runtime-version) and rebuild our app.
### Unexpected branch
If the deployment has an unexpected branch, we need to [map our channel to the correct branch](/#map-channel-to-branch).
### Missing updates
The displayed deployment does not have any updates. To fix this, [publish an update to the branch](/#publish-update). If an update was already published, check the [Updates page](https://expo.dev/accounts/[account]/projects/[project]/updates) to make sure it matches the runtime version of our build.
### Missing branch
The displayed deployment has the correct channel, but it is not linked to a branch. To fix this, [map the channel to the correct branch](/#map-channel-to-branch).
### Missing deployment
If our deployment is not displayed, it means our build is not configured properly for EAS Update. To fix this, [configure our channel](/#configure-channel), [configure our runtime version](/#configure-runtime-version) and verify our [general configuration](/eas-update/debug-advanced/#verifying-app-configuration). We'll need to rebuild our app after making these changes.
### Automatic roll back when an update crashes
If everything looks correct on the Deployments page, but your app still shows the previous update or the code embedded with the build, your new update's code may be crashing. This can happen when this new update crashes before the root component renders after the app downloads and applies the new update.
EAS Update is designed to automatically roll back to the previous update if it detects that a new update crashed shortly after launch. See [how EAS Update detects crashes and rolls back to a previous working version](/eas-update/error-recovery/#explaining-the-error-recovery-flow) for more information.
To diagnose the error causing the update crash:
- See the [Troubleshooting guide on runtime issues](/debugging/runtime-issues/) to apply a strategy to identify the error.
- After identifying the error, publish a new update that fixes the crash to resolve the issue.
A common reason a new update does not work but embedded code does is due to a missing environment variable. See [how environment variables work with EAS Update](/eas-update/environment-variables/) for more information.
## Solutions
### Configure channel
To verify that a build has a specific channel, make sure our build profile in **eas.json** has a channel property:
```json eas.json
{
"build": {
"preview": {
"distribution": "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".
### Configure runtime version
To verify our runtime version, we make sure our app config (**app.json**/**app.config.js**) has a `runtimeVersion` property:
```json app.json
{
"expo": {
"runtimeVersion": {
"policy": "sdkVersion"
}
}
}
```
By default, it is `{ "policy": "sdkVersion" }`, but we can change our runtime to [use a different policy or a specific version](/eas-update/runtime-versions). Then, we can run a command like `eas build --profile preview` to create a build with the runtime version we expect.
### Map channel to branch
If the channel is not mapped to the branch we expect, we can change the link with:
```sh
$ eas channel:edit production --branch release-1.0
```
If our branch is not listed, we can create a new branch with `eas branch:create`.
### Publish update
To create and publish an update, we can run the following command:
```sh
$ eas update
```
After publishing, the output will display the branch and the runtime version. This information can help us verify that we're creating an update with the configuration we expect.
## General strategies
Try these strategies before using the more specific ones mentioned in this guide.
### Use `expo-dev-client`
Create a [development version of our build](/eas-update/expo-dev-client). It will help us preview published updates inside a problematic build.
### In-app debugging
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 make a simulator build of the project and manually check to see if updates are available or if there are errors when fetching updates.
- Print the [Update.Constants](/versions/latest/sdk/updates/#constants) to verify our configuration.
- [Examine log entries](/versions/latest/sdk/updates/#updatesreadlogentriesasyncmaxage) surfaced from the native layer.
- Fetch and [load updates manually](/versions/latest/sdk/updates/#check-for-updates-manually).
## Configuration issues
Our app is still not receiving the expected update despite following the [basic guide](/eas-update/debug).
### `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.
#### Verifying app configuration
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](https://expo.dev/accounts/[account]/projects/[project]).
- `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:
```sh
$ npx expo install expo-updates
```
#### Inspecting expo-updates configuration after prebuild
Whenever we run `eas build`, the `npx expo prebuild` command is run on our project on EAS servers to unpack the **android** and **ios** directories that contain native files. This makes 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 `npx 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 the properties we'd expect to see in each file:
```xml AndroidManifest.xml
```
```xml Expo.plist
EXUpdatesRuntimeVersionyour-runtime-version-hereEXUpdatesURLhttps://u.expo.dev/your-project-id-here
```
### Configuration without EAS Build
If we aren't using EAS Build, this section will walk through debugging 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.
#### Verify build configuration
Follow the [Building Locally guide](/eas-update/standalone-service/) to configure our app's channel and runtime version. We'll also need to make sure our [general configuration](/eas-update/debug-advanced/#expo-updates-configuration) is correct.
#### Verify the channel
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"`.
Once a build has a channel name, we can make sure that EAS servers know about it by checking the [Channels page](https://expo.dev/accounts/[account]/projects/[project]/channels).
We'd expect the page to display the same channel name that our build has. If it's not there, we can create the channel on EAS servers with:
```sh
$ eas channel:create production
```
#### Verify the channel/branch mapping
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.
The [Channels page](https://expo.dev/accounts/[account]/projects/[project]/channels) will display the channel to branch mapping if it exists.
If the channel is not linked to the branch we expect, we can change the link with:
```sh
$ eas channel:edit production --branch release-1.0
```
#### Verify the update
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 go to the [Branches page](https://expo.dev/accounts/[account]/projects/[project]/branches) and choose our branch of interest.
The Branch Detail page 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.
## Debugging EAS Update
After verifying `expo-updates` and EAS Update configurations, we can move on to debugging how our project is interacting with updates.
### In-app debugging
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 make a simulator build of the project and manually check to see if updates are available or if there are errors when fetching updates. See the code example to [check for updates manually](/versions/latest/sdk/updates/#use-expo-updates-with-a-custom-server).
### Inspecting a build manually
When building a project into an app, there can be multiple steps that alter the output of `npx 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:
```xml Expo.plist
EXUpdatesRequestHeadersexpo-channel-nameyour-channel-nameEXUpdatesRuntimeVersionyour-runtime-versionEXUpdatesURLhttps://u.expo.dev/your-project-id
```
### Inspecting manifests manually
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 we could visit would be similar to this:
```text
https://u.expo.dev/your-project-id?runtime-version=1.0.0&channel-name=production&platform=android
```
### Viewing network requests
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 program like [Proxyman](https://proxyman.io/) or [Charles Proxy](https://www.charlesproxy.com/) 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 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, and so on, 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 manifest request:
## Runtime issues
We are able to load the expected update but our project is displaying unexpected behavior.
### Debugging of native code while loading the app through expo-updates
By default, we need to make a release build for `expo-updates` to be enabled and to load updates rather than reading from a development server. This is because debug builds behave like normal React Native project debug builds.
To make it easier to test and debug native code in an environment that is closer to production, follow the steps below to create a debug build of the app with `expo-updates` enabled.
We also provide a [step-by-step guide to try out EAS Update quickly](/eas-update/standalone-service/) in a local development environment using Android Studio or Xcode, with either release or debug builds of the app.
#### Android local builds
- Set the debug environment variable: `export EX_UPDATES_NATIVE_DEBUG=1`
- [Ensure the desired channel is set in your **AndroidManifest.xml**](/eas-update/getting-started/#configure-the-update-channel)
- Execute a [debug build](/debugging/runtime-issues/#native-debugging) of the app with Android Studio or from the command line.
#### iOS local builds
- Set the debug environment variable: `export EX_UPDATES_NATIVE_DEBUG=1`
- Reinstall pods with `npx pod-install`. The `expo-updates` podspec now detects this environment variable, and makes changes so that the debug code that would normally load from the Metro packager is bypassed, and the app is built with the EXUpdates bundle and other dependencies needed to load updates from EAS.
- [Ensure the desired channel is set in our **Expo.plist**](/eas-update/getting-started/#configure-the-update-channel)
- Modify the application Xcode project file to force bundling of the application JavaScript for both release and debug builds:
```sh
$ sed -i '' 's/SKIP_BUNDLING/FORCE_BUNDLING/g;' ios/.xcodeproj/project.pbxproj
```
- Execute a [debug build](/debugging/runtime-issues/#native-debugging) of the app with Xcode or from the command line.
#### EAS Build
Alternatively, we can use EAS to create a debug build where `expo-updates` is enabled. The environment variable is set in **eas.json**, as shown in the example below:
```json eas.json
{
"build": {
"preview_debug": {
"env": {
"EX_UPDATES_NATIVE_DEBUG": "1"
},
"android": {
"distribution": "internal",
"withoutCredentials": true,
"gradleCommand": ":app:assembleDebug"
},
"ios": {
"simulator": true,
"buildConfiguration": "Debug"
},
"channel": "preview_debug"
}
}
}
```
## Publishing issues
We are not able to publish an update, or parts of our update are not being published as expected.
### Inspecting the latest update locally
When we publish an update with EAS Update, it creates a **/dist** directory in the root of our project locally, which includes the assets that were uploaded as a part of the update.
### Viewing all assets included in an update
It may be helpful to see which assets are included in our update bundle. We can see a list of named assets by running:
```sh
$ npx expo export
```
## Mitigation steps
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.
### Re-publishing a previous update
The fastest way to "undo" a bad publish is to re-publish a known good update. Imagine we have a branch with two updates:
```sh
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:
```sh
$ eas update:republish --group abc1
$ eas update:republish --branch production
```
The example command above would result in a branch that now appears like this:
```sh
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.
## Error recovery
Learn how to take advantage of using built-in error recovery when using expo-updates library.
Apps using `expo-updates` can take advantage of built-in error recovery behavior as an extra safeguard against accidentally publishing broken updates.
While we cannot stress enough the importance of testing updates in a staging environment before publishing to production, humans (and even computers) occasionally make mistakes, and the error recovery behavior described here can serve as a last resort in such cases.
> **warning** **Disclaimer:** The behavior documented below is subject to change and should not be relied upon. Always test your code carefully and thoroughly in production-like environments before publishing updates.
## Help! I published a broken update to production. What should I do?
First of all, don't panic. Mistakes happen; most likely, everything will be fine.
The important thing is to **publish a new update with a fix as soon as possible (though not before you are 100% confident in your fix).** The error recovery mechanism in `expo-updates` will ensure that in most cases, even users who have already downloaded the broken update should be able to get the fix.
The first thing to try is rolling back to an older update that you know was working. **However, this may not always be safe;** your broken update may, for example, have modified persistent state (such as data stored in AsyncStorage or on the device's file system) in a non-backwards-compatible way. It's important to test in a staging environment that emulates an end user's device state as closely as possible to load the broken update and then roll back.
If you can identify an older update that is safe to roll back to, you can do so using EAS Update's `republish` option from [Expo dashboard](https://expo.dev/accounts/[account]/projects/[project]/updates) or [EAS CLI](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch).
If you cannot identify an older update that is safe to roll back to, you'll need to fix it forward. While it's best to roll out a fix as quickly as possible, you should take the time to ensure your fix is solid, and know that even users who download your broken update in the meantime should be able to download your fix.
If you'd like more details about how this works, read on.
## Explaining the error recovery flow
The error recovery flow is intended to be as lightweight as possible. It is not a full safety net that protects your end users from the results of errors; in many cases, users will still see a crash.
Rather, the purpose is to prevent updates from "bricking" your app (causing a crash on launch before the app can check for updates, making the app unusable until uninstalled and reinstalled) by ensuring that in as many cases as possible, the app has the opportunity to download a new update and fix itself.
### Catching an error
If your app throws a fatal error when executing JS which is early enough in the app's lifecycle that it may prevent the app from being able to download further updates, `expo-updates` will catch this error.
> If more than 10 seconds have elapsed between your app's first render and the time a fatal error is thrown, `expo-updates` will not catch this error at all and none of the error recovery code will be triggered. Therefore, we highly recommend that your app check for updates very shortly after launching, whether automatically or manually to ensure you can push out fixes in the event of a future error.
If `expo-updates` catches a JS error, what will happen next depends on whether React Native has fired the native "content appeared" event (`ReactMarkerConstants.CONTENT_APPEARED` on Android or `RCTContentDidAppearNotification` on iOS) — approximately when your app's first view has been rendered on the screen, for this particular update, either on this launch or a previous one.
> **Why this distinction?** In some cases `expo-updates` may try to automatically roll back to an older (working) update, but this can be dangerous if your new update has modified persistent state in a non-backwards compatible way. We assume that if the error occurs before the first view has rendered, no such code has been able to execute, and so rolling back is safe. After this point `expo-updates` will only fix forward and will not roll back.
### If content has appeared
If an error is caught and the "content appeared" event has already fired, OR if it has ever been fired on this device on a past launch of the same update, the following will happen:
- A 5 second timer will be started, and (unless `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` is set to `NEVER`) the app will check for a new update and download it if there is one.
- If there is no new update, the update finishes downloading, or the timer runs out (whichever happens first), the app will throw the original error and crash.
Note that if a new update is downloaded, it will launch when the user next tries to open the app.
### If content has not appeared
If an error is caught before the "content appeared" event has fired, and this is the first time the current update is being launched on this device, the following will happen:
- The update will be marked as "failed" locally and will not be launched again on this device.
- A 5 second timer will be started, and (unless `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` is set to `NEVER`) the app will check for a new update and download it if there is one.
- If a new update finishes downloading before the timer runs out, the app will immediately try to reload itself and launch the newly downloaded update.
- If this newly downloaded update also throws a fatal error, or there is no new update, or the timer runs out, the app will immediately try to reload by rolling back to an older update, whichever one was most recently launched successfully.
- If this also fails, or there is no older update available on the device, the app will throw the original error and crash.
## Error stacktraces
If your app encounters a fatal JS error, and the error recovery system cannot recover, it will re-throw the original exception to cause a crash. The stacktrace will look similar to this:
```text
--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: expo-updates-error-recovery
AndroidRuntime: Process: com.myapp.MyApp, PID: 12498
AndroidRuntime: com.facebook.react.common.JavascriptException
AndroidRuntime:
AndroidRuntime: at com.facebook.react.modules.core.ExceptionsManagerModule.reportException(ExceptionsManagerModule.java:72)
AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime: at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372)
AndroidRuntime: at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188)
AndroidRuntime: at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:938)
AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99)
AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27)
AndroidRuntime: at android.os.Looper.loop(Looper.java:223)
AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:228)
AndroidRuntime: at java.lang.Thread.run(Thread.java:923)
```
On Android, the stacktrace of the original exception is preserved. Depending on your crash reporting service, you may or may not need to reproduce the crash locally to see more information about the underlying error.
```text
Last Exception Backtrace:
0 CoreFoundation 0xf203feba4 __exceptionPreprocess + 220 (NSException.m:200)
1 libobjc.A.dylib 0xf201a1be7 objc_exception_throw + 60 (objc-exception.mm:565)
2 MyApp 0x10926b7ee -[EXUpdatesAppController throwException:] + 24 (EXUpdatesAppController.m:422)
3 MyApp 0x109280352 -[EXUpdatesErrorRecovery _crash] + 984 (EXUpdatesErrorRecovery.m:222)
4 MyApp 0x10927fa3d -[EXUpdatesErrorRecovery _runNextTask] + 148 (EXUpdatesErrorRecovery.m:0)
5 libdispatch.dylib 0x109bc1848 _dispatch_call_block_and_release + 32 (init.c:1517)
6 libdispatch.dylib 0x109bc2a2c _dispatch_client_callout + 20 (object.m:560)
7 libdispatch.dylib 0x109bc93a6 _dispatch_lane_serial_drain + 668 (inline_internal.h:2622)
8 libdispatch.dylib 0x109bca0bc _dispatch_lane_invoke + 392 (queue.c:3944)
9 libdispatch.dylib 0x109bd6472 _dispatch_workloop_worker_thread + 648 (queue.c:6732)
10 libsystem_pthread.dylib 0xf6da2845d _pthread_wqthread + 288 (pthread.c:2599)
11 libsystem_pthread.dylib 0xf6da2742f start_wqthread + 8
```
Even though it appears the exception was thrown from expo-updates, this stacktrace generally indicates **an error that originated in JavaScript**.
Unfortunately, Apple's crash reporting does not include the exception message, which details the underlying error and its location in JavaScript. To see the message and help you narrow down the issue, you may need to reproduce the crash locally with the Xcode debugger or macOS Console app attached.
# Reference
## End-to-end code signing with EAS Update
Learn how code signing and key rotation work in EAS Update.
> **info** EAS Update Code Signing is only available to accounts subscribed to the EAS Production or Enterprise plans. [Learn more](https://expo.dev/pricing).
The `expo-updates` library supports end-to-end code signing using [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). Code signing allows developers to cryptographically sign their updates with their own keys. The signatures are then verified on the client before the update is applied, which ensures ISPs, CDNs, cloud providers, and even EAS itself cannot tamper with updates run by apps.
The following steps will guide you through the process of generating a private key and corresponding certificate, configuring your project to use code signing, and publishing a signed update for your app.
Step 1:
## Generate a private key and corresponding certificate
In this step, we will generate a key pair and corresponding code signing certificate for your app. Specify a directory outside of your source control for the `--key-output-directory` flag to ensure the generated private key isn't accidentally added to source control.
```sh
$ npx expo-updates codesigning:generate \\
--key-output-directory ../keys \\
--certificate-output-directory certs \\
--certificate-validity-duration-years 10 \\
--certificate-common-name "Your Organization Name"
]}
cmdCopy='npx expo-updates codesigning:generate --key-output-directory ../keys --certificate-output-directory certs --certificate-validity-duration-years 10 --certificate-common-name "Your Organization Name"'
/>
This command generated a key pair along with a code signing certificate to be included in the app:
- `../keys/private-key.pem`: the private key of the key pair.
- `../keys/public-key.pem`: the public key of the key pair.
- `certs/certificate.pem`: the code signing certificate configured to be valid for 10 years. This file should be added to source control (if applicable).
- The generated private key must be kept private and secure. The command above suggests generating and storing these keys in a directory outside of your source control to ensure they are not accidentally checked into source control. We recommend storing the private key in the same way you would store other sensitive information (KMS
password manager
and so on)
and how you store it will vary the steps needed to publish an update in step (3).
- The public key may be stored alongside the private key
but isn't sensitive.
- The certificate should be included in the project (checked into source control). It contains the public key and a method for verifying code signatures. When a signed update is downloaded
the update's signature is verified using this certificate.
- The certificate validity duration is a setting that may vary based on the security needs of your app.
- A shorter validity duration will require [key rotation](#key-rotation) more frequently but is considered better practice since a compromised private key will have a sooner expiration which limits exposure.
- Shorter validity durations add overhead to your app's release process as the key must be rotated more frequently. Binaries with expired certificates won't apply new updates.
- For example
Expo sets this value to 20 years for the public Expo Go app
but only 1 year for internal apps with binaries that are distributed more frequently. We plan to rotate our keys every 10 years.
Step 2:
## Configure your project to use code signing
` element in **android/app/src/main/AndroidManifest.xml**
Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of **certs/certificate.pem** and replace all of the `\r` characters with `
` and `\n` with `
` manually, or run the following script to do that for you:
Now add the following two fields, and replace the `android:value` for the `expo.modules.updates.CODE_SIGNING_CERTIFICATE` field with the XML-escaped certificate. You do not need to modify the value for `expo.modules.updates.CODE_SIGNING_METADATA` entry.
```xml android/app/src/main/AndroidManifest.xml
```
---
Note: Configure code signing in an iOS native project
---
You will need to add two fields to the `` element in **ios/project-name/Supporting/Expo.plist**.
Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of **certs/certificate.pem** and replace all of the `\r` characters with `
`, or run the following script to do that for you:
Now add the following two fields, and replace the certificate value with the XML-escaped certificate. You do not need to update the `EXUpdatesCodeSigningMetadata` field.
```xml ios/project-name/Supporting/Expo.plist
EXUpdatesCodeSigningCertificate-----BEGIN CERTIFICATE-----
(insert XML-escaped certificate, it should look something like this)
(spanning multiple lines with \r escaped but \n not escaped)
+-----END CERTIFICATE-----
EXUpdatesCodeSigningMetadatakeyidmainalgrsa-v1_5-sha256
```
---
With code signing configured, create a new build with a new runtime version. The code signing certificate will be embedded in this new build.
Step 3:
## Publish a signed update for your app
```sh
$ eas update --private-key-path ../keys/private-key.pem
```
During an EAS Update publish using `eas update`, the EAS CLI automatically detects that code signing is configured for your app. It then verifies the integrity of the update and creates a digital signature using your private key. This process is performed locally so that your private key never leaves your machine. The generated signature is automatically sent to EAS to store alongside the update.
Step 4:
## Verify that the update is loaded
Download the update on the client (this step is done automatically by the library). The build from step (2) that is configured for code signing checks if there is a new update available. The server responds with the update published in step (3) and its generated signature. After being downloaded but before being applied, the update is verified against the embedded certificate and included signature. The update is applied if the certificate and signature are valid, and rejected otherwise.
## Additional information
### Key rotation
Key rotation is the process by which the key pair used for signing updates is changed. This is most commonly done in a few cases:
- Key expiration. In step (1) from the section above, we set `certificate-validity-duration-years` to 10 years (though it can be configured to any value). This means that after 10 years, updates signed with the private key corresponding to the certificate will no longer be applied after being downloaded by the app. Updates downloaded before the expiration of their signing certificate will continue to function normally. Rotating keys well before the certificate expires helps to preempt any potential key expiration issues and helps to guarantee all users are using the new certificate before the old certificate expires.
- Private key compromise. If the private key used to sign updates is accidentally exposed to the public, it can no longer be considered secure and therefore the integrity of updates signed with it can no longer be guaranteed. For example, a malicious actor could craft a malicious update and sign it with the leaked private key.
- Key rotation for security best practices. It is best practice to rotate keys periodically to ensure that a system is resilient to manual key rotation in response to one of the other reasons above.
In any of these cases, the procedure is similar:
1. Back up the old keys and certificate that were generated in step (1) above.
2. Generate a new key by following the steps above starting from step (1). To assist in debugging, you may wish to change the `keyid` of the new key by modifying the `updates.codeSigningMetadata.keyid` field in your app config (**app.json**).
3. The code signing certificate is part of the app's runtime, so a new runtime version should be set for builds using this certificate to ensure that only updates signed with the new key run in the new build.
4. Publish signed updates using the new key by following step (3) above.
### Removing code signing
The process of removing code signing from an app is similar to [key rotation](#key-rotation) and can be thought of as a key rotation to a `null` key.
1. Backup the old key and certificate that were generated in step (1) above.
2. Remove the `updates.codeSigningMetadata` field from your app config (**app.json**).
3. The new certificate-less app is a new distinct runtime, so a new runtime version should be set for builds to ensure that only unsigned updates run in the new build.
## Asset selection and exclusion
Learn how to use the asset selection feature and verify that an update includes all required app assets.
> **info** Available for SDK 50 and above ([`expo-updates`](/versions/latest/sdk/updates/) >= 0.23.0).
SDK 50 added the experimental **asset selection feature**, which allows the developer to specify that only certain assets should be included in updates. This can greatly reduce the number of assets that need to be uploaded to and downloaded from the updates server. This feature will work with the EAS Update server or any custom server that complies with the [`expo-updates` protocol](/technical-specs/expo-updates-1).
SDK 52 launched this feature to general availability.
## Using asset selection
To use asset selection in SDK 51 and below, include the property `extra.updates.assetPatternsToBeBundled` in your app config. It should define one or more file-matching patterns (regular expressions). For example, an **app.json** file has the patterns defined in the following way:
```json app.json
"expo": {
"extra": {
"updates": {
"assetPatternsToBeBundled": [
"app/images/**/*.png"
]
}
}
}
```
To use asset selection in SDK 52 and above, include the property `updates.assetPatternsToBeBundled` in your app config. It should define one or more file-matching patterns (regular expressions). For example, an **app.json** file has the patterns defined in the following way:
```json app.json
"expo": {
"updates": {
"assetPatternsToBeBundled": [
"app/images/**/*.png"
]
}
}
```
After adding this configuration all **.png** files in all subdirectories of **app/images** will be included in updates. You have to also ensure that these assets need to be required in your JavaScript code.
If `assetPatternsToBeBundled` isn't included in the app config, all assets resolved by the bundler will be included in updates (as per SDK 49 and earlier behavior).
## Verifying that an update includes all required app assets
When using the asset selection, assets that do not match any file patterns will resolve in the Metro bundler. However, these assets will not be uploaded to the updates server. You have to be certain that assets not included in updates are built into the native build of the app.
If you are building your app locally or have access to the correct build for publishing updates (with the same [runtime version](/eas-update/runtime-versions/)), then use the `npx expo-updates assets:verify` command. It allows you to check whether all required assets will be included when you publish an update:
```sh
$ npx expo-updates assets:verify
```
> **info** This new command is part of the `expo-updates` CLI, which also supports [EAS Update code signing](/eas-update/code-signing/). It is not part of the [Expo CLI](/more/expo-cli/) or the [EAS CLI](https://github.com/expo/eas-cli). Only available for ([`expo-updates`](/versions/latest/sdk/updates/) >= 0.24.10).
You can also use the `--help` option with the command to see the available options:
| Option | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `` | Directory of the Expo project. Default: Current working directory. |
| `-a, --asset-map-path ` | Path to the **assetmap.json** in an export produced by the command `npx expo export --dump-assetmap` . |
| `-e, --exported-manifest-path ` | Path to the **metadata.json** in an export produced by the command `npx expo export --dump-assetmap`. |
| `-b, --build-manifest-path ` | Path to the **app.manifest** file created by `expo-updates` in an Expo application build (either **android** or **ios**). |
| `-p, --platform ` | Options: ["android", "ios"] |
| `-h, --help` | Usage info. |
## Example
}
Icon={GithubIcon}
href="https://github.com/expo/UpdatesAPIDemo"
/>
## Using EAS Update without other EAS services
Learn how to use EAS Update independently of other EAS services, such as Build.
EAS Update works great as a standalone service, so you can use it with or without EAS Build and other EAS services. All of its main features are designed to be agnostic of the build pipeline, and its used in production by large organizations that do not use other EAS services.
Note: What are the downsides of using EAS Update without other EAS services?
---
EAS Update and Build work closely together to provide an experience that is greater than the sum of its parts. For example, when you create a build with EAS Build we will help with the bookkeeping for various aspects related to updates, such as the runtime version and channel.
Builds that use the same channel and runtime version are grouped into a **Deployments** section on [expo.dev](https://expo.dev/accounts/[account-name/projects/[project-name]/deployments). These sorts of bookkeeping and insights features that depend on knowledge of builds or other aspects of your app won't be available if you use EAS Update independently of other EAS services.
That said, many organizations are already heavily invested in their CI/CD infrastructure or may have other reasons for wanting to use another build pipeline, and the benefits offered by deeper integration across EAS services may not be worth the switching costs of migrating to a different CI/CD provider.
---
## Using EAS Update without EAS Build
Most of the [installation and configuration steps](/eas-update/getting-started) are identical whether or not you use EAS Build. The primary difference is how the update [channel](/eas-update/eas-cli/) is configured. When using EAS Build, the channel from **eas.json** will automatically be added to your build's **AndroidManifest.xml** and **Expo.plist** at build time. When not using EAS Build, this must be configured manually by [setting the request header in the app config](/eas-update/getting-started/#configure-update-channels-in-appjson), followed by manually creating the channel on the server.
```sh
which points to the production EAS Update branch by default)
$ eas channel:create production
```
## Migrate from CodePush
A guide to help migrate from CodePush to EAS Update.
This guide explains how to transition a React Native project that uses CodePush to use EAS Update which offers [many advantages](/eas-update/introduction/#pitch). It assumes that you're using the default React Native project structure. For assistance with migrating brownfield native apps to EAS Update, see [Using EAS Update in an existing native app](/eas-update/integration-in-existing-native-apps/).
To learn more about the differences between CodePush and EAS Update, see [Conceptual differences between CodePush and EAS Update](#conceptual-differences-between-codepush-and-eas-update) and the [What to do without CodePush post on the Expo Blog](https://expo.dev/blog/what-to-do-without-codepush).
Step 1:
## Ensure your app is using the latest Expo SDK version
To migrate from CodePush to EAS Update, we recommend that you use the latest Expo SDK version. Instructions are not available for older Expo SDK and React Native version. While you may be able to migrate successfully by adapting the instructions as needed for the older Expo SDK and React Native version that your app uses, additional hands-on support for integrating with older versions can only be provided for enterprise customers ([contact us](https://expo.dev/contact)).
Step 2:
## Uninstall CodePush
To avoid conflicts and unexpected behavior, it's recommended to uninstall CodePush if you're using EAS Update. This is because your app could periodically fetch updates from both services, leading to issues, especially if you're using different configurations for each service.
Remove the CodePush SDK from your project by uninstalling the `react-native-code-push` package:
```sh
$ npm uninstall react-native-code-push
```
You'll also need to remove CodePush references from JS and native code. See this [GitHub comment](https://github.com/Microsoft/react-native-code-push/issues/1101#issuecomment-350204507) for more detailed instructions.
Step 3:
## Add an `expo` key to your `app.json`
Ensure that your project has an **app.json** file with an `expo` object. If you don't have anything specific to configure in your **app.json** file yet, you can create a minimal file with an empty `expo` object like this:
```json app.json
{
"expo": {
//... any other existing keys you have
}
}
```
Step 4:
## Follow the "Getting Started" guide
The instructions in the [EAS Update Getting Started guide](/eas-update/getting-started/) will guide you through setting up EAS Update in your project.
Step 5:
## Resubmit your app
Since you have changed the update provider from CodePush to EAS Update, you will need to rebuild your app and submit the new build to the respective app stores (Google Play Store and Apple App Store) to ensure the update mechanism works as expected for your end-users.
Follow the respective store guidelines for submitting a new build of your application:
- [Submitting to Google Play Store](/submit/android)
- [Submitting to Apple App Store](/submit/ios)
After successfully submitting your app, users will be able to download and use the latest build with EAS Update integration. If your app is not updating as expected, [validate your configuration](/eas-update/debug).
## Common questions
Note: How do I release mandatory/critical updates with EAS Update?
---
CodePush CLI has a `--mandatory` flag that allows you to release mandatory updates. You can build this functionality with EAS Update but there is no specific flag for it.
[Learn more about mandatory/critical updates](/eas-update/download-updates/#criticalmandatory-updates).
---
Note: How do I include a message in an update?
---
CodePush CLI has a `--description` flag that allows you to include a message in an update. You can build this functionality with EAS Update using the `extra` field in your app config.
Refer the `--message` flag in this example: [`expo/UpdatesAPIDemo`](https://github.com/expo/UpdatesAPIDemo).
---
Note: How do I switch the 'deployment' that is being used at runtime, similar to the sync() function in CodePush?
---
This is possible using `Updates.setUpdateURLAndRequestHeadersOverride()`. Learn more in the [Override update configuration at runtime](/eas-update/override/) guide.
---
Note: How do I handle different environments (such as staging and production) with EAS Update?
---
With EAS Update, you can use channels and branches to manage different environments and rollouts. [Learn more](/eas-update/eas-cli/).
---
Note: How do I roll back updates with EAS Update?
---
You can roll back updates using `eas update:rollback`. Learn more in the [Rollback to a previous update](/eas-update/rollbacks/) guide.
---
Note: How do I gradually roll out updates with EAS Update?
---
EAS Update supports various strategies for gradually rolling out updates, so you can pick which approach best fits your needs. [Learn more](/eas-update/rollouts/).
---
Note: How can I have direct control over when an update is downloaded and applied?
---
Learn more about different strategies for downloading and applying updates in the [Downloading updates](/eas-update/download-updates/) guide, such as checking for updates while the app is running or even when backgrounded with `Updates.checkForUpdateAsync()`.
---
Note: Does EAS Update support end-to-end code signing?
---
Yes, EAS Update supports end-to-end code signing. It is available for EAS Production and Enterprise plan subscribers. Learn more in the [Code signing](/eas-update/code-signing/) guide.
---
Note: What else should I know about?
---
- Expo Orbit: The macOS, Windows, and Linux desktop launcher app. You can [launch updates](/review/with-orbit/) directly from the website with it in one click, among other features.
- You can monitor the adoption of updates from the EAS website. [Learn more](/eas-update/download-updates/#monitoring-adoption-of-updates). You can also roll out and roll back updates from the website.
- You can use EAS Update to achieve a web-like preview workflow. [Learn more](/eas-update/preview).
- Each update and build created with EAS is associated with a [fingerprint](/versions/latest/sdk/fingerprint/). You can diff these fingerprints through the website UI or with `eas fingerprint:compare` to see what changed in the native runtime of your app between your builds and updates, to understand build/update compatibility, and guide your decision about when to bump the [`runtimeVersion`](/eas-update/runtime-versions/).
---
## Conceptual differences between CodePush and EAS Update
CodePush and EAS Update are both services that allow you to send hotfixes to the JavaScript code of your app, but they take slightly different approaches, and so you may need to adapt your release process when moving to EAS Update.
Note: Differences in how updates are organized within streams
---
**CodePush has single streams of updates for deployments**. What this means is that you can point a build to a deployment, and it will pull updates from that. If you want to change the deployment that is targeted by a build, you can do this at runtime through a JavaScript API.
**EAS Update has multiple streams of updates** — one that correspond to your source control branches (called branches), and another called channels, which point to branches. The mapping between channels and branches is handled on the server side, and a channel can point to different branches for each runtime version (additionally, more advanced logic may be expressed, such as to support incremental rollouts). Builds are not directly associated with branches, but rather with channels. Each build points to a single channel, which is set at build time and cannot be modified at runtime. The reason for this is that it ensures that certain branches (for example: development, staging) don't automatically go out to production — your preview updates don't go to your production users. This helps you separate the two main uses of EAS Update: previews and production hotfixes.
---
Note: Differences in how updates are selected at runtime
---
A key distinction between CodePush and EAS Update that can impact your release process is that **with CodePush, the client controls the target update deployment at runtime**, and **with EAS Update, it is controlled on the server side, by mapping channels to branches**. This means that you can't include code in your app using EAS Update to instruct it to load a different stream of updates depending on runtime criteria, such as the current user role (for example: distribute beta releases to employees) - it will only load the branch that is mapped on EAS Update servers to the corresponding channel (such as production or staging).
The ability to control the target deployment at runtime is commonly used with CodePush in staging environments to allow non-technical stakeholders to test features from a single build on Google Play Beta / TestFlight. The current alternative for this with EAS Update is to use [development builds](/eas-update/expo-dev-client/). We're currently working on providing a way to do this with release builds.
---
## Migrate from Classic Updates
A guide to help migrate from Classic Updates to EAS Update.
> SDK 49 was the last version to support Classic Updates. To continue using the deprecated `expo publish` command, set [`updates.useClassicUpdates`](/versions/latest/config/app/#useclassicupdates) in your app config.
EAS Update is the next generation of Expo's updates service. If you're using Classic Updates, this guide will help you upgrade to EAS Update.
## Prerequisites
EAS Update requires the following versions or greater:
- Expo CLI >= 5.3.0
- EAS CLI >= 0.50.0
- Expo SDK >= 45.0.0
- expo-updates >= 0.13.0
## Install EAS CLI
Step 1:
Install EAS CLI:
```sh
$ npm install --global eas-cli
```
Step 2:
Then, log in with your expo account:
```sh
$ eas login
```
## Configure your project
You'll need to make the following changes to your project:
Step 1:
Initialize your project with EAS Update:
```sh
$ eas update:configure
```
After this command, you should have two new fields in your app config at `expo.updates.url` and `expo.runtimeVersion`.
Step 2:
To ensure that updates are compatible with the underlying native code inside a build, EAS Update uses a new field named `runtimeVersion` that replaces the `sdkVersion` field in your project's app config. Remove the `expo.sdkVersion` property from your app config.
Step 3:
To allow updates to apply to builds built with EAS, update your EAS Build profiles in **eas.json** to include `channel` properties. These channels replace `releaseChannel` properties. We find it convenient to name the `channel` after the profile's name. For instance, the `preview` profile has a `channel` named `"preview"` and the `production` profile has a `channel` named `"production"`.
```json eas.json
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"channel": "production"
}
}
}
```
Step 4:
**Optional**: If your project is a bare React Native project, see [Use EAS Update in an existing project](/eas-update/getting-started/) for the extra configuration you may need.
## Create new builds
The changes above affect the native code layer inside builds, which means you'll need to make new builds to start sending updates. Once your builds are complete, you'll be ready to publish an update.
## Publish an update
After making a change to your project locally, you're ready to publish an update, run:
```sh
$ eas update --channel [channel-name] --message [message]
$ eas update --channel production --message "Fixes typo"
```
Once published, you can see the update in the [Expo dashboard](https://expo.dev/accounts/[account]/projects/[project]/updates).
## Additional migration steps
- Replace instances of `expo publish` with `eas update` in scripts. You can view all the options for publishing with `eas update --help`.
- If you have any code that references `Updates.releaseChannel` from the `expo-updates` library, replace them with `Updates.channel`.
- Remove any code that references `Constants.manifest`. That will now always return `null`. You can access most properties you'll need with `Constants.expoConfig` from the `expo-constants` library.
## Learn more
The steps described above allow you to use a similar flow to Classic Updates. However, EAS Update is more flexible and has more features. It can be used to create more stable release flows. Learn [how EAS Update works](/eas-update/how-it-works) and how you can craft a more stable [deployment process](/eas-update/deployment-patterns) for your project and your team.
If you experience issues with migrating, check out our [debugging guide](/eas-update/debug). If you have feedback, join us on Discord in the #update channel.
## How to trace an update ID back to the Expo Dashboard
Learn how to trace an update ID back to the Expo Dashboard when using EAS Update and expo-updates library.
When working with [EAS Updates](/eas-update/introduction/), you might encounter a scenario where you need to trace an `updateId` back to the [Expo dashboard](https://expo.dev/accounts/[account]). This can be challenging because `Updates.updateId` always returns an ID, regardless of whether [`Updates.isEmbeddedLaunch`](/versions/latest/sdk/updates/#updatesisembeddedlaunch) is `true` or `false`. However, if the app is running an embedded update, attempting to look up the `updateId` in the [Expo dashboard](https://expo.dev/accounts/[account]) will result in an error. This happens because embedded updates are not tracked in the dashboard.
## Determine if the update is embedded or downloaded
To avoid this issue, you can use the `Updates.isEmbeddedLaunch` property to determine whether the app is running an embedded update or one downloaded from the server. If `Updates.isEmbeddedLaunch` is `true`, the currently running update is embedded in the build, which means it won't be available in the Expo dashboard.
Here's an example of how you can display whether the update is embedded or downloaded:
```tsx UpdateStatus.tsx
import * as Updates from 'expo-updates';
import { Text } from 'react-native';
export default function UpdateStatus() {
return (
{Updates.isEmbeddedLaunch
? '(Embedded) ❌ You cannot trace this update in the Expo dashboard.'
: '(Downloaded) ✅ You can trace this update in the Expo dashboard.'}
);
}
```
When you navigate to an update group in the Expo dashboard (open your project, select **Updates**, and click a specific update), the URL displays as:
```text
https://expo.dev/accounts/[accountName]/projects/[projectName]/updates/[updateGroupId]
```
You can replace `updateGroupId` with the `Updates.updateId` to navigate directly to a specific platform update:
```text
https://expo.dev/accounts/[accountName]/projects/[projectName]/updates/[updateId]
```
This opens the corresponding update group for the platform-specific update.
## Estimate bandwidth usage
Learn how to estimate bandwidth usage for EAS Update.
## Understanding update bandwidth usage
EAS Update enables an app to update its own non-native pieces (such as JavaScript, styling, and images) over-the-air. This guide explains how bandwidth is consumed and how consumption can be optimized.
## Bandwidth calculation breakdown
Each subscription plan includes a predefined bandwidth allocation per monthly billing period in addition to its monthly active user (MAU) allocation ([learn more about how MAU are calculated](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle)). MAU's beyond the standard allocation are billed at [usage-based pricing rates](https://expo.dev/pricing#update), and each of those additional MAU add an extra 40 MiB to your standard bandwidth allocation. This bandwidth determines the number of updates your users can download before being charged for additional bandwidth usage.
Here's how to estimate bandwidth usage per update:
- **Update size:** The key factor in bandwidth consumption is the size of update assets that are not already on the device. If an update only modifies the JavaScript portion of your app, users will only download the new JavaScript. To begin an example, let's say the uncompressed JavaScript portion generated during export is **10 MB**. Compression will further reduce its size.
- **Compression ratio:** The level of compression depends on the file type. JavaScript and Hermes bytecode (commonly used in React Native apps) can be compressed, while images and icons are not automatically compressed. In the example above, a Hermes bytecode bundle achieves an estimated **2.6× compression ratio**, reducing the actual download size to:
```text
10 MB / 2.6 ≈ 3.85 MB update bandwidth size
```
Given a bandwidth allocation, we estimate how many updates can be downloaded in a monthly billing period before additional bandwidth charges apply. For example, if you have 60,000 MAUs on a production plan, it includes 50,0000 MAU and **1 TiB (1,024 GiB) of bandwidth per month**. An additional 10,000 MAUs purchased through usage-based pricing receives an additional **40 MiB of bandwidth per MAU**. The total number of updates that can be downloaded is:
```text
(1,024 GiB × 1,024 MiB/GiB) + (10,000 MAU × 40 MiB/MAU) = 1,448,576 MiB per month
1,448,576 MiB / 3.85 MiB ≈ 376,254 updates
```
## Measuring your actual update size
To determine the actual compressed size of your Hermes bundle, run the following commands:
```sh
$ brotli -5 -k bundle.hbc
$ gzip -9 -k bundle.hbc
$ ls -lh bundle.hbc.br bundle.hbc.gz
```
This will generate **Brotli- and Gzip-compressed** versions of your Hermes bundle (**bundle.hbc.br** and **bundle.hbc.gz**) and display their sizes. You can use this to refine bandwidth calculations based on your app's real-world update sizes.
## Factors that affect bandwidth consumption
Actual bandwidth usage varies due to:
- **User behavior:** Theoretical calculations assume every user downloads every update. However, many users only get updates when they reopen the app, often skipping intermediate updates. As a result, actual bandwidth usage is usually much lower than the theoretical maximum.
- **Missing assets:** If an update includes assets such as fonts and images that are not already on the device from the build or previously downloaded updates, they will need to be downloaded as well.
## Optimizing bandwidth usage
1. **Monitor usage first:** The easiest way to manage bandwidth is to track your [usage metrics](https://expo.dev/accounts/[account]/settings/usage) and identify any unusual spikes or inefficiencies.
2. **Optimize asset size:** Reduce the size of your assets with [this guide](/eas-update/optimize-assets).
3. **Exclude assets when needed:** Use [asset selection](/eas-update/asset-selection) to reduce the number of assets included with each update. This is an advanced optimization and other approaches should be tried first.
## Using EAS Update in an existing native app
Learn how to integrate EAS Update into your existing native Android and iOS app to enable over-the-air updates.
> **info** If your project is a **greenfield React Native app** — primarily built with React Native from the start, and the entry point of the app is React Native, then skip this guide and proceed to [Get started with EAS Update](/eas-update/getting-started/).
This guide explains how to integrate EAS Update in an existing native app, sometimes referred to as a brownfield app. It assumes that you are using Expo SDK 52 and React Native 0.76.
Instructions are not available for older Expo SDK and React Native version. Additional hands-on support for integrating with older versions can only be provided for enterprise customers ([contact us](https://expo.dev/contact)).
## Prerequisites
> **warning** The following instructions may not work for all projects. The specifics of integrating EAS Update into existing projects depend heavily on the specifics of your app, and so you may need to adapt the instructions to your unique setup. If you encounter issues, [create an issue on GitHub](https://github.com/expo/expo/issues) or open a pull request to suggest improvements to this guide.
You should have a brownfield native project with React Native installed and configured to render a root view. If you don't have this yet, follow the [Integration with Existing Apps](https://reactnative.dev/docs/integration-with-existing-apps) guide from the React Native documentation and then come back here once you have followed the steps.
- Your app must be using the [latest Expo SDK version and its supported React Native version](/versions/latest/#each-expo-sdk-version-depends-on-a-react-native-version).
- Remove any other update library integration from your app, such as react-native-code-push, and ensure that your app compiles and runs successfully in both debug and release on your supported platforms.
- Support for Expo modules (through the `expo` package) must be installed and configured in your project. [Learn more](/brownfield/installing-expo-modules/).
- Your **metro.config.js** [must extend `expo/metro-config`](/guides/customizing-metro/#customizing) .
- Your **babel.config.js** [must extend `babel-preset-expo`](/versions/latest/config/babel/).
- The command `npx expo export -p android` must run successfully in your project if it supports Android, and `npx expo export -p ios` if it supports iOS.
## Installation and basic configuration
Follow steps 1, 2, 3, and 4 from the [Get started with EAS Update](/eas-update/getting-started/) guide.
After this is complete, you will have installed and authenticated with `eas-cli`, installed `expo-updates` to your project, initialized an associated EAS project, and added basic configuration to your native projects.
## Opt out of automatic setup
The next step is to disable the default behavior of `expo-updates` to automatically set itself up in a way that supports greenfield React Native projects.
### Disable automatic setup on Android
Modify **android/settings.gradle** to set the property that disables automatic updates initialization, as in the example below:
### Disable automatic setup on iOS
Pass in the environment variable to CocoaPods installation to disable automatic updates initialization.
```sh
$ EX_UPDATES_CUSTOM_INIT=1 npx pod-install
```
## Set up your React Native app to use expo-updates for loading the release bundle
The next step is to integrate `expo-updates` into your Android and iOS projects so that your app will use `expo-updates` as the source of your app JavaScript in release builds.
Note: Example
---
A complete working example is available at [this GitHub repository](https://github.com/expo/CustomRNView) .
---
### Integrating expo-updates with your React Native bundling
1. Ensure that your Metro config extends the Expo config, as in this example:
```js metro.config.js
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname); // eslint-disable-line no-undef
// Make any custom changes you need for your project by
// directly modifying "config"
module.exports = config;
```
2. If you are using a custom entry point, be sure to include Expo initialization there. This ensures that Expo libraries (including `expo-updates`) are all initialized properly. Here are two examples:
```jsx First Custom Entry Point file example
// Expo recommends using registerRootComponent().
// It registers the component with the react-native AppRegistry,
// and performs all required Expo initialization
// (including expo-updates setup)
import App from './App';
import { registerRootComponent } from 'expo';
registerRootComponent(App);
```
```jsx Second custom entry point file example
// If you need to keep an existing entry point that uses AppRegistry directly,
// you will need to add a call to Expo's initialization before registering the
// app, as shown below.
import App from './App';
import 'expo/src/Expo.fx';
import { AppRegistry } from 'react-native';
function getApp() {
return ;
}
AppRegistry.registerComponent('App', () => getApp());
```
### Integrating expo-updates on Android
The following instructions assume you have an app written in Kotlin, with one or more native activities. Open **android/app/src/main/java/com/\/MainActivity.kt** and follow the steps below.
1. Your React Native activity should subclass `com.facebook.react.ReactActivity`.
2. In this activity, add code to `onCreate()` to initialize the updates system. The initialization should not happen in the main thread (otherwise a lockup and ANR will occur).
3. Override `getMainComponentName()` to return the name of the app you registered in your JS entry point above.
4. Show the React Native view, by overriding the `createReactActivityDelegate()` method as shown below.
```kotlin android/app/src/main/java/com//MainActivity.kt
package com.yourpackagename
import android.content.Context
import android.os.Bundle
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import expo.modules.ReactActivityDelegateWrapper
import expo.modules.updates.UpdatesController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
// Step 1
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
CoroutineScope(Dispatchers.IO).launch {
startUpdatesController(applicationContext)
}
}
// Step 2
private fun startUpdatesController(context: Context) {
UpdatesController.initialize(context)
// Call the synchronous `launchAssetFile()` function to wait for updates ready
UpdatesController.instance.launchAssetFile
}
// Step 3
override fun getMainComponentName(): String = "App"
// Step 4
override fun createReactActivityDelegate(): ReactActivityDelegate {
return ReactActivityDelegateWrapper(
this,
BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
object : DefaultReactActivityDelegate(
this,
mainComponentName,
fabricEnabled
) {})
}
}
```
### Integrating expo-updates on iOS
The following instructions assume you have an app written in Swift, with one or more native screens that have custom UIViewControllers. We will add a custom view controller that renders your React Native app.
#### AppDelegate changes
1. Modify **AppDelegate.swift** so that it extends `EXAppDelegateWrapper`.
2. If you are not already doing so, add a public method to get the running `AppDelegate` instance, so that your custom view controller can access it later.
3. Add a reference to the singleton instance of the `expo-updates` `AppController` class, which manages the updates system on iOS.
4. Override the `bundleUrl()` method to return the correct bundle URL for updates, if the updates system is running.
5. The `didFinishLaunchingWithOptions()` method needs to perform two steps:
1. Initialize the root view factory used later to create the React Native root view.
2. Call `AppController.initializeWithoutStarting()` . This creates the controller instance, but defers the rest of the updates startup procedure until it is needed.
```swift ios//AppDelegate.swift
import ExpoModulesCore
import EXUpdates
import React
import UIKit
@UIApplicationMain
// Step 1
class AppDelegate: EXAppDelegateWrapper {
let bundledUrl = Bundle.main.url(forResource: "main", withExtension: "jsbundle")
var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
// Step 2
public static func shared() -> AppDelegate {
guard let delegate = UIApplication.shared.delegate as? AppDelegate else {
fatalError("Could not get app delegate")
}
return delegate
}
// Step 3
var updatesController: (any InternalAppControllerInterface)?
// Step 4
override func bundleURL() -> URL? {
if let updatesUrl = updatesController?.launchAssetUrl() {
return updatesUrl
}
return bundledUrl
}
// Step 5
private func initializeReactNativeAndUpdates(_ launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
self.launchOptions = launchOptions
self.moduleName = "App"
self.initialProps = [:]
self.rootViewFactory = createRCTRootViewFactory()
AppController.initializeWithoutStarting()
}
/**
* Application launch initializes the custom view controller; all React Native
* and updates initialization is handled there
*/
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
initializeReactNativeAndUpdates(launchOptions)
return true
}
}
```
#### Implementing a custom view controller
1. The view controller should implement the updates protocol `AppControllerDelegate`.
2. The view controller initialization should
1. Set the app delegate's updates controller instance, so that its `bundleURL()` method above works correctly for updates.
2. Set the `AppController` delegate to the view controller instance
3. Start the `AppController`
3. Finally, the view controller must implement the one method in the `AppControllerDelegate` protocol, `appController(_ appController: AppControllerInterface, didStartWithSuccess success: Bool)`. This method will be called once the updates system is fully initialized, and the latest update (or the embedded bundle) is ready to be rendered.
1. Create the React Native root view using the root view factory created by the app delegate. The app name passed in must match the app name that you registered in your JS entry point above.
2. Add this root view to the view controller.
```swift ios//CustomViewController.swift
import UIKit
import EXUpdates
import ExpoModulesCore
// Step 1
public class CustomViewController: UIViewController, AppControllerDelegate {
let appDelegate = AppDelegate.shared()
// Step 2
public convenience init() {
self.init(nibName: nil, bundle: nil)
self.view.backgroundColor = .clear
// Step 2.1
appDelegate.updatesController = AppController.sharedInstance
// Step 2.2
AppController.sharedInstance.delegate = self
// Step 2.3
AppController.sharedInstance.start()
}
required public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
@available(*, unavailable)
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Step 3
public func appController(
_ appController: AppControllerInterface,
didStartWithSuccess success: Bool
) {
createView()
}
private func createView() {
// Step 3.1
let rootView = appDelegate.rootViewFactory.view(
withModuleName: appDelegate.moduleName ?? "main",
initialProperties: appDelegate.initialProps,
launchOptions: appDelegate.launchOptions
)
// Step 3.2
self.view.addSubview(rootView)
let controller = self
controller.view.clipsToBounds = true
controller.view.addSubview(rootView)
rootView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
rootView.topAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.topAnchor),
rootView.bottomAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.bottomAnchor),
rootView.leadingAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.leadingAnchor),
rootView.trailingAnchor.constraint(equalTo: controller.view.safeAreaLayoutGuide.trailingAnchor)
])
}
}
```
## Common questions
Note: How long will this take to add to my app?
---
Assuming you are using the latest version of React Native supported by the Expo SDK, and you are comfortable with the React Native integration in your native projects, then you can likely integrate EAS Update in a similar amount of time as it would take you to integrate with a tool like CodePush or Sentry.
The most important factor is the React Native version that your app uses. If your app uses anything older than the latest supported version by the Expo SDK (as referenced at the top of this guide), then you will want to upgrade to that version first, and the time that will take is heavily dependent on the size and complexity of the app and skill and experience level of the team working on it.
---
Note: I'm migrating from CodePush, what else do I need to know?
---
To learn more, see [Migrating from CodePush](/eas-update/codepush/) guide.
---
## EAS Update FAQ
Frequently asked questions about EAS Update.
### What guidelines must I follow when publishing updates?
One of the rules of EAS Update is that you need to follow the rules of the platforms and app stores you are building for. This means your updates need to follow the App Store and Play Store guidelines, including the content of the updates and how you use them. This usually means changes to your app's behavior need to be reviewed.
App Store rules regularly change and just like how you might need to follow them if you were writing an app without Expo, you need to follow them when you are using Expo and EAS Update.
EAS Update is a great way to quickly deliver improvements to the people who use your apps. For example, consider an app that has a critical bug that needs to be fixed. With EAS Update you can quickly get the fix out and later follow up with a new submission that includes the fix built in.
### How are "monthly active users" counted for a billing cycle?
> **info** 1 monthly active user equals 1 unique installation of an app that downloads at least 1 update during your billing cycle.
- An app install that downloads a new update on each day of your billing cycle counts as 1 monthly active user.
- An app install that does not download any new updates during your billing cycle counts as 0 monthly active users.
- Uninstalling and reinstalling an app (and downloading updates in each during your billing cycle) counts as 2 monthly active users.
- A single device that has two apps owned by a single Expo account, both of which use updates, is considered 2 monthly active users for the account.
### How can I implement a custom update strategy for my app?
By default, `expo-updates` checks for updates every time the app is loaded. You can implement a custom update strategy with the [Updates API](/versions/latest/sdk/updates) and [app config](/versions/latest/config/app/#updates).
### Are Classic Updates still supported?
The Classic Updates service is now deprecated. It was available before December 2021, and `expo publish` cannot be run anymore. However, existing apps will continue to receive Classic Updates that have already been published and are actively used.
We recommend transitioning to EAS Update or using a [self-hosted update service](/versions/latest/sdk/updates/).
# EAS Metadata
## Introduction
## Get started with EAS Metadata
Learn how to automate and maintain your app store presence from the command line with EAS Metadata.
> **warning** EAS Metadata is in preview and subject to breaking changes.
EAS Metadata enables you to automate and maintain your app store presence from the command line. It uses a [**store.config.json**](./config.mdx#static-store-config) file containing all required app information instead of going through multiple different forms. It also tries to find common pitfalls that could cause app rejections with built-in validation.
## Prerequisites
EAS Metadata currently **only supports the Apple App Store**.
> Using VS Code? Install the [Expo Tools extension](https://github.com/expo/vscode-expo#readme) for auto-complete, suggestions, and warnings in your **store.config.json** files.
## Create the store config
Let's start by creating our **store.config.json** file in the root directory of your project. This file holds all the information you want to upload to the app stores.
If you already have an app in the stores, you can pull the information into a store config by running:
```sh
$ eas metadata:pull
```
If you don't have an app in the stores yet, EAS Metadata can't generate the store config for you. Instead, create a new store config file.
```json store.config.json
{
"configVersion": 0,
"apple": {
"info": {
"en-US": {
"title": "Awesome App",
"subtitle": "Your self-made awesome app",
"description": "The most awesome app you have ever seen",
"keywords": ["awesome", "app"],
"marketingUrl": "https://example.com/en/promo",
"supportUrl": "https://example.com/en/support",
"privacyPolicyUrl": "https://example.com/en/privacy"
}
}
}
}
```
> By default, EAS Metadata uses the **store.config.json** file at the root of your project. You can change the name and location of this file by setting the **eas.json** [`metadataPath`](../../submit/eas-json.mdx#metadatapath) property.
## Update the store config
Now it's time to edit the **store.config.json** file and customize it to your app needs. You can find all available options in the [store config schema](/eas/metadata/schema/).
## Upload a new app version
Before pushing the **store.config.json** to the app stores, you must upload a new binary of your app. For more information, see [uploading new binaries to stores](/submit/introduction/).
After the binary is submitted and processed, you can push the store config to the app stores.
## Upload the store config
When you are satisfied with the **store.config.json** settings, you can push it to the app stores by running the following command:
```sh
$ eas metadata:push
```
If EAS Metadata runs into any issues with your store config, it will warn you when running this command. When there are no errors, or you confirm to push it with possible issues, it will try to upload as much as possible.
When the store config partially fails, you can change the store config and retry. `eas metadata:push` can be used to retry pushing the missing items.
## Next steps
# Reference
## Configuring EAS Metadata
Learn about different ways to configure EAS Metadata.
> **warning** EAS Metadata is in preview and subject to breaking changes.
EAS Metadata is configured by a **store.config.json** file at the _root of your project_.
You can configure the path or name of the store config file with the **eas.json** [`metadataPath`](../../submit/eas-json.mdx#metadatapath) property.
Besides the default JSON format, EAS Metadata also supports more dynamic config using JavaScript files.
## Static store config
The default store config type for EAS Metadata is a simple JSON file.
The code snippet below shows an example store config with basic App Store information written in English (U.S.).
You can find all configuration options in the [store config schema](/eas/metadata/schema/).
> If you have the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme) installed, you get auto-complete, suggestions, and warnings for **store.config.json** files.
```json store.config.json
{
"configVersion": 0,
"apple": {
"info": {
"en-US": {
"title": "Awesome App",
"subtitle": "Your self-made awesome app",
"description": "The most awesome app you have ever seen",
"keywords": ["awesome", "app"],
"marketingUrl": "https://example.com/en/promo",
"supportUrl": "https://example.com/en/support",
"privacyPolicyUrl": "https://example.com/en/privacy"
}
}
}
}
```
## Dynamic store config
At times, Metadata properties can benefit from dynamic values. For example, the Metadata **copyright notice** should contain the current year. This can be automated with EAS Metadata.
To generate content dynamically, start by creating a JavaScript config file **store.config.js**. Then, use the [`metadataPath`](/eas/json/#metadatapath) property in the **eas.json** file to pick the JS config file.
> `eas metadata:pull` can't update dynamic store config files. Instead, it creates a JSON file with the same name as the configured file. You can import the JSON file to reuse the data from `eas metadata:pull`.
```js
// Use the data from `eas metadata:pull`
const config = require('./store.config.json');
const year = new Date().getFullYear();
config.apple.copyright = `${year} Acme, Inc.`;
module.exports = config;
```
```json
{
"submit": {
"production": {
"ios": {
"metadataPath": "./store.config.js"
}
}
}
}
```
## Store config with external content
When using external services for localizations, you have to fetch external content.
EAS Metadata supports synchronous and asynchronous functions exported from dynamic store config files.
The function results are awaited before validating and syncing with the stores.
> The **store.config.js** function is evaluated in Node.js. If you need special values, like secrets, use environment variables.
```js
// Use the data from `eas metadata:pull`
const config = require('./store.config.json');
module.exports = async () => {
const year = new Date().getFullYear();
const info = await fetchLocalizations('...').then(response => response.json());
config.apple.copyright = `${year} Acme, Inc.`;
config.apple.info = info;
return config;
};
```
```json
{
"submit": {
"production": {
"ios": {
"metadataPath": "./store.config.js"
}
}
}
}
```
## Schema for EAS Metadata
A reference of store config in EAS Metadata.
> **warning** EAS Metadata is in preview and subject to breaking changes.
The store config in EAS Metadata contains information that otherwise would be provided manually through the app store dashboards.
This document outlines the structure of the object in your store config.
> If you use the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme), you get all this information through auto-complete, suggestions, and warnings in your editor.
## Config schema
An essential property in the store config object is the `configVersion` property.
App stores might require more or change existing information structures to publish your app.
This property helps versioning changes that are not backward compatible.
EAS Metadata _currently_ only supports the Apple App Store.
{[
{
name: 'configVersion',
type: 'number',
rules: ['enum: 0'],
description: 'The EAS Metadata store configuration schema version.',
},
{
name: 'apple',
type: 'object',
description: 'All configurable properties for the App Store.',
},
{
nested: 1,
name: 'version',
type: 'string',
description: [
The app version to use when syncing all metadata defined in the store config.,
EAS Metadata selects the latest available version in the app stores by default.
,
],
},
{
nested: 1,
name: 'copyright',
type: 'string',
description:
'The name of the person or entity that owns the exclusive rights to the app, preceded by the year the rights were obtained. (for example, "2008 Acme Inc.")',
},
{
nested: 1,
name: 'advisory',
type: AppleAdvisory,
description: "The App Store questionnaire to determine the app's age rating.",
},
{
nested: 1,
name: 'categories',
type: AppleCategories,
description:
'App Store categories for the app. You can add primary, secondary, and possible subcategories.',
},
{
nested: 1,
name: 'info',
type: (
<>
{'Map<'}
AppleLanguage
{', '}
AppleInfo
{'>'}
>
),
description: 'The localized App Store presence of your app.',
},
{
nested: 1,
name: 'release',
type: AppleRelease,
description: 'The app release strategy for the selected version.',
},
{
nested: 1,
name: 'review',
type: AppleReview,
description:
'All required information to review the app for the App Store review team, including contact info and credentials. (if applicable)',
},
]}
### Apple advisory
Apple uses a complex questionnaire to determine the app's [age rating](https://help.apple.com/app-store-connect/#/dev599d50efb).
Parental controls on the App Store use this calculated age rating.
EAS Metadata uses the least restrictive answer for each of these questions by default.
Note: Complete advisory with the least restrictive answers
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"advisory": {
"alcoholTobaccoOrDrugUseOrReferences": "NONE",
"contests": "NONE",
"gamblingSimulated": "NONE",
"horrorOrFearThemes": "NONE",
"matureOrSuggestiveThemes": "NONE",
"medicalOrTreatmentInformation": "NONE",
"profanityOrCrudeHumor": "NONE",
"sexualContentGraphicAndNudity": "NONE",
"sexualContentOrNudity": "NONE",
"violenceCartoonOrFantasy": "NONE",
"violenceRealistic": "NONE",
"violenceRealisticProlongedGraphicOrSadistic": "NONE",
"gambling": false,
"unrestrictedWebAccess": false,
"kidsAgeBand": null,
"ageRatingOverride": "NONE",
"koreaAgeRatingOverride": "NONE"
}
}
}
```
---
{[
{
name: 'alcoholTobaccoOrDrugUseOrReferences',
type: AppleAgeRating,
description: 'Does the app contain alcohol, tobacco, or drug use or references?',
},
{
name: 'contests',
type: AppleAgeRating,
description: 'Does the app contain contests?',
},
{ name: 'gambling', type: 'boolean', description: 'Does your app contain gambling?' },
{
name: 'gamblingSimulated',
type: AppleAgeRating,
description: 'Does the app contain simulated gambling?',
},
{
name: 'horrorOrFearThemes',
type: AppleAgeRating,
description: 'Does the app contain horror or fear themes?',
},
{
name: 'kidsAgeBand',
type: AppleKidsAge,
description: [
When parents visit the Kids category on the App Store, they expect the apps they find will
protect their children's data, provide only age-appropriate content, and require a
parental gate to link out of the app, request permissions, or present purchasing
opportunities.
,
It's critical that no personally identifiable information or device information be
transmitted to third parties, and that advertisements are human-reviewed for age
appropriateness to be displayed.
,
Learn more
,
],
},
{
name: 'matureOrSuggestiveThemes',
type: AppleAgeRating,
description: 'Does the app contain mature or suggestive themes?',
},
{
name: 'medicalOrTreatmentInformation',
type: AppleAgeRating,
description: 'Does the app contain medical or treatment information?',
},
{
name: 'profanityOrCrudeHumor',
type: AppleAgeRating,
description: 'Does the app contain profanity or crude humor?',
},
{
name: 'ageRatingOverride',
type: AppleAgeRatingOverride,
description: [
If your app rates 12+ or lower, and you believe its content may not be suitable for
children, you can manually override the age rating.
,
Learn more
,
],
},
{
name: 'koreaAgeRatingOverride',
type: (
AppleKoreaAgeRatingOverride
),
description: [
If your app rates 12+ or lower, and you believe its content may not be suitable for
children, you can manually override the age rating. Same as `ageRatingOverride`, but for
South Korea.
,
Learn more
,
],
},
{
name: 'sexualContentGraphicAndNudity',
type: AppleAgeRating,
description: 'Does the app contain graphic sexual content and nudity?',
},
{
name: 'sexualContentOrNudity',
type: AppleAgeRating,
description: 'Does the app contain sexual content or nudity?',
},
{
name: 'unrestrictedWebAccess',
type: 'boolean',
description:
'Does your app contain unrestricted web access, such as with an embedded browser?',
},
{
name: 'violenceCartoonOrFantasy',
type: AppleAgeRating,
description: 'Does the app contain cartoon or fantasy violence?',
},
{
name: 'violenceRealistic',
type: AppleAgeRating,
description: 'Does the app contain realistic violence?',
},
{
name: 'violenceRealisticProlongedGraphicOrSadistic',
type: AppleAgeRating,
description: 'Does the app contain prolonged graphic or sadistic realistic violence?',
},
]}
#### Apple advisory age rating
{[
{ name: 'NONE', description: 'For apps that do not use the subject whatsoever.' },
{
name: 'INFREQUENT_OR_MILD',
description: 'For apps mentioning the subject or using the subject as a non-primary feature.',
},
{
name: 'FREQUENT_OR_INTENSE',
description: 'For apps using the subject as a primary feature.',
},
]}
#### Apple advisory kids age
{[
{ name: 'FIVE_AND_UNDER', description: 'For kids of 5 years old and below.' },
{ name: 'SIX_TO_EIGHT', description: 'For kids between the age of 6 to 8 years.' },
{ name: 'NINE_TO_ELEVEN', description: 'For kids between the age of 9 to 11 years.' },
]}
#### Apple advisory age rating override
{[
{ name: 'NONE', description: 'No age rating override' },
{
name: 'SEVENTEEN_PLUS',
description:
'App contains content that may not be suitable for children under the age of 17.',
},
{
name: 'UNRATED',
description:
'Adults Only. This content cannot be published on the App Store. It may be published on alternative app marketplaces on iOS or websites in the European Union.',
},
]}
#### Apple advisory korea age rating override
{[
{ name: 'NONE', description: 'No age rating override' },
{
name: 'FIFTEEN_PLUS',
description:
'App contains content that may not be suitable for children under the age of 15.',
},
{
name: 'NINETEEN_PLUS',
description:
'App contains content that may not be suitable for children under the age of 19.',
},
]}
### Apple categories
The App Store helps users discover new apps by [categorizing apps into categories](https://developer.apple.com/app-store/categories/), using primary, secondary, and possible subcategories.
Note: Primary and secondary category
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"categories": ["FINANCE", "NEWS"]
}
}
```
---
Note: Primary, subcategories, and secondary category
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"categories": [["GAMES", "GAMES_CARD", "GAMES_BOARD"], "ENTERTAINMENT"]
}
}
```
---
{[
{
name: 'BOOKS',
description:
'Apps with content that is traditionally offered in printed form and which provide additional interactivity.',
},
{
name: 'BUSINESS',
description:
'Apps that assist with running a business or provide a means to collaborate, edit, or share business-related content.',
},
{
name: 'DEVELOPER_TOOLS',
description: 'Apps that assist users with developing, maintaining, or sharing software.',
},
{
name: 'EDUCATION',
description:
'Apps that provide an interactive learning experience on specific skills or subjects.',
},
{
name: 'ENTERTAINMENT',
description:
'Interactive apps designed to entertain the user with audio, visual, or other content.',
},
{
name: 'FINANCE',
description:
'Apps that provide financial services or information to assist users with business or personal finances.',
},
{
name: 'FOOD_AND_DRINK',
description:
'Apps that provide recommendations, instruction, or reviews related to preparing, consuming, or reviewing food or beverages.',
},
{
name: 'GAMES',
description: [
Apps that provide single or multiplayer interactive experiences for entertainment
purposes.
,
This category can have up to 2 subcategories.,
{[
'GAMES_ACTION',
'GAMES_ADVENTURE',
'GAMES_BOARD',
'GAMES_CARD',
'GAMES_CASINO',
'GAMES_CASUAL',
'GAMES_FAMILY',
'GAMES_MUSIC',
'GAMES_PUZZLE',
'GAMES_RACING',
'GAMES_ROLE_PLAYING',
'GAMES_SIMULATION',
'GAMES_SPORTS',
'GAMES_STRATEGY',
'GAMES_TRIVIA',
'GAMES_WORD',
]}
,
],
},
{
name: 'GRAPHICS_AND_DESIGN',
description:
'Apps that provide tools or tips for creating, editing, or sharing visual content.',
},
{
name: 'HEALTH_AND_FITNESS',
description:
'Apps related to healthy living, including stress management, fitness, and recreational activities.',
},
{
name: 'LIFESTYLE',
description: 'Apps related to a general-interest subject matter or service.',
},
{
name: 'MAGAZINES_AND_NEWSPAPERS',
description:
'Apps with journalistic content that is traditionally offered in printed form and which provide additional interactivity.',
},
{
name: 'MEDICAL',
description:
'Apps focused on medical education, information, or health reference for patients or healthcare professionals.',
},
{
name: 'MUSIC',
description:
'Apps that are for discovering, listening, recording, performing, or composing music.',
},
{
name: 'NAVIGATION',
description: 'Apps that provide information to help a user get to a physical location.',
},
{
name: 'NEWS',
description:
'Apps that provide information about current events and/or developments in areas of interest such as politics, entertainment, business, science, technology, and other areas.',
},
{
name: 'PHOTO_AND_VIDEO',
description:
'Apps that assist in capturing, editing, managing, storing, or sharing photos and videos.',
},
{
name: 'PRODUCTIVITY',
description: 'Apps that make a specific process or task more organized or efficient.',
},
{
name: 'REFERENCE',
description: 'Apps that assist the user in accessing or retrieving general information.',
},
{ name: 'SHOPPING', description: 'Apps that provide a means to purchase goods or services.' },
{
name: 'SOCIAL_NETWORKING',
description: 'Apps that connect people through text, voice, photo, or video.',
},
{
name: 'SPORTS',
description:
'Apps related to professional, amateur, collegiate, or recreational sporting activities.',
},
{
name: 'STICKERS',
description: [
Apps that provide extended visual functionality to messaging apps.,
This category can have up to 2 subcategories.,
{[
'STICKERS_ANIMALS',
'STICKERS_ART',
'STICKERS_CELEBRATIONS',
'STICKERS_CELEBRITIES',
'STICKERS_CHARACTERS',
'STICKERS_EATING_AND_DRINKING',
'STICKERS_EMOJI_AND_EXPRESSIONS',
'STICKERS_FASHION',
'STICKERS_GAMING',
'STICKERS_KIDS_AND_FAMILY',
'STICKERS_MOVIES_AND_TV',
'STICKERS_MUSIC',
'STICKERS_PEOPLE',
'STICKERS_PLACES_AND_OBJECTS',
'STICKERS_SPORTS_AND_ACTIVITIES',
]}
,
],
},
{
name: 'TRAVEL',
description:
'Apps that assist the user with any aspect of travel, such as planning, purchasing, or tracking.',
},
{
name: 'UTILITIES',
description: 'Apps that enable the user to solve a problem or complete a specific task.',
},
{ name: 'WEATHER', description: 'Apps with specific weather-related information.' },
]}
### Apple info
The App Store is a global service used by many people in different languages.
You can localize your App Store presence in [multiple languages](#apple-info-languages).
Note: Minimal localized info in English (U.S.)
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"info": {
"en-US": {
"title": "Awesome app",
"privacyPolicyUrl": "https://example.com/en/privacy"
}
}
}
}
```
---
Note: Complete localized info written in English (U.S.)
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"info": {
"en-US": {
"title": "App title",
"subtitle": "Subtitle for your app",
"description": "A longer description of what your app does",
"keywords": ["keyword", "other-keyword"],
"releaseNotes": "Bug fixes and improved stability",
"promoText": "Short tagline for your app",
"marketingUrl": "https://example.com/en",
"supportUrl": "https://example.com/en/help",
"privacyPolicyUrl": "https://example.com/en/privacy",
"privacyChoicesUrl": "https://example.com/en/privacy/choices"
}
}
}
}
```
---
{[
{
name: 'title',
type: 'string',
rules: ['length: 2..30'],
description: [
Name of the app in the store. This name should be similar to the installed app name.
,
The name will be reviewed before it is made available on the App Store.
,
],
},
{
name: 'subtitle',
type: 'string',
rules: ['length: 30'],
description: [
Subtext for the app in the store. For example, "A Fun Game For Friends".,
The subtitle will be reviewed before it is made available on the App Store.
,
],
},
{
name: 'description',
type: 'string',
rules: ['length: 10..4000'],
description: 'The main description of what the app does',
},
{
name: 'keywords',
type: 'string[]',
rules: ['unique items', 'max length item: 100'],
description: 'List of keywords to help users find the app in the App Store',
},
{
name: 'releaseNotes',
type: 'string',
rules: ['max length: 4000'],
description: 'Changes since the last public version',
},
{
name: 'promoText',
type: 'string',
rules: ['max length: 170'],
description: 'The short tagline for the app',
},
{
name: 'marketingUrl',
type: 'string',
rules: ['max length: 255'],
description: 'URL to the app marketing page',
},
{
name: 'supportUrl',
type: 'string',
rules: ['max length: 255'],
description: 'URL to the app support page',
},
{ name: 'privacyPolicyText', type: 'string', description: 'Privacy policy for Apple TV' },
{
name: 'privacyPolicyUrl',
type: 'string',
rules: ['max length: 255'],
description: [
URL that links to your privacy policy.,
A privacy policy is required for all apps.,
],
},
{
name: 'privacyChoicesUrl',
type: 'string',
rules: ['max length: 255'],
description:
'URL where users can modify and delete the data collected from the app or decide how their data is used and shared.',
},
]}
#### Apple info languages
{[
{ name: 'Arabic', description: 'ar-SA' },
{ name: 'Catalan', description: 'ca' },
{
name: 'Chinese',
description: [
zh-Hans (Simplified)
,
zh-Hant (Traditional)
,
],
},
{ name: 'Croatian', description: 'hr' },
{ name: 'Czech', description: 'cs' },
{ name: 'Danish', description: 'da' },
{ name: 'Dutch', description: 'nl-NL' },
{
name: 'English',
description: [
en-AU (Australia)
,
en-CA (Canada)
,
en-GB (U.K.)
,
en-US (U.S.)
,
],
},
{ name: 'Finnish', description: 'fi' },
{
name: 'French',
description: [
fr-CA (Canada)
,
fr-FR (France)
,
],
},
{ name: 'German', description: 'de-DE' },
{ name: 'Greek', description: 'el' },
{ name: 'Hebrew', description: 'he' },
{ name: 'Hindi', description: 'hi' },
{ name: 'Hungarian', description: 'hu' },
{ name: 'Indonesian', description: 'id' },
{ name: 'Italian', description: 'it' },
{ name: 'Japanese', description: 'ja' },
{ name: 'Korean', description: 'ko' },
{ name: 'Malay', description: 'ms' },
{ name: 'Norwegian', description: 'no' },
{ name: 'Polish', description: 'pl' },
{
name: 'Portuguese',
description: [
pt-BR (Brazil)
,
pt-PT (Portugal)
,
],
},
{ name: 'Romanian', description: 'ro' },
{ name: 'Russian', description: 'ru' },
{ name: 'Slovak', description: 'sk' },
{
name: 'Spanish',
description: [
es-MX (Mexico)
,
es-ES (Spain)
,
],
},
{ name: 'Swedish', description: 'sv' },
{ name: 'Thai', description: 'th' },
{ name: 'Turkish', description: 'tr' },
{ name: 'Ukrainian', description: 'uk' },
{ name: 'Vietnamese', description: 'vi' },
]}
### Apple release
There are multiple strategies to put the app in the hands of your users.
You can release the app automatically after store approval or gradually release an update to your users.
Note: Automatic release after 25th of December, 2022 (UTC)
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"release": {
"automaticRelease": "2022-12-25T00:00:00+00:00"
}
}
}
```
---
{[
{
name: 'automaticRelease',
type: 'boolean|Date',
description: [
If and how the app should automatically be released after approval from the App Store.
,
false - Manually release the app after store approval. (default behavior)
true - Automatically release after store approval.
Date - Automatically schedule release on this date after store approval (using the{' '}
RFC 3339
{' '}
format).
,
Apple does not guarantee that your app is available at the chosen scheduled release date.
,
],
},
{
name: 'phasedRelease',
type: 'boolean',
description: [
Phased release for automatic updates lets you gradually release this update over a 7-day
period to users who have turned on automatic updates.
,
Keep in mind that this version will still be available to all users as a manual update
from the App Store.
,
You can pause the phased release for up to 30 days or release this update to all users at
any time.
,
Learn more
,
],
},
]}
### Apple review
Before publishing the app on the App Store, store approval is required.
The App Store review team must have all the information to test your app, or you risk an app rejection.
Note: Minimal review information
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"review": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+1 123 456 7890"
}
}
}
```
---
Note: Complete review information
---
```json store.config.json
{
"configVersion": 0,
"apple": {
"review": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "+1 123 456 7890",
"demoUsername": "john",
"demoPassword": "applereview",
"demoRequired": false,
"notes": "This is an example app primarily used for educational purposes."
}
}
}
```
---
{[
{
name: 'firstName',
type: 'string',
rules: ['min length: 1'],
description:
"The app contact's first name in case communication is needed with the App Store review team is needed.",
},
{
name: 'lastName',
type: 'string',
rules: ['min length: 1'],
description:
"The app contact's last name in case communication is needed with the App Store review team is needed.",
},
{
name: 'email',
type: 'string',
rules: ['email'],
description:
'Email contact address in case communication is needed with the App Store review team.',
},
{
name: 'phone',
type: 'string',
description: [
Contact phone number in case communication is needed with the App Store review team.
,
Preface the phone number with "+" followed by the country code. (for example, +44 844 209
0611)
,
],
},
{
name: 'demoUsername',
type: 'string',
description: 'The user name to sign in to your app to review its features.',
},
{
name: 'demoPassword',
type: 'string',
description: 'The password to sign in to your app to review its features.',
},
{
name: 'demoRequired',
type: 'boolean',
description: [
A Boolean value indicates if sign-in information is required to review your app's
features.
,
If users sign in using social media, provide information for an account for review.
,
Credentials must be valid and active for the duration of the review.
,
],
},
{
name: 'notes',
type: 'string',
rules: ['length: 2..4000'],
description: [
Additional information about your app that can help during the review process.,
Do not include demo account details in the notes. Use the demoUsername{' '}
and demoPassword properties instead.
,
],
},
]}
## EAS Metadata FAQ
Frequently asked questions about EAS Metadata.
> **warning** EAS Metadata is in preview and subject to breaking changes.
## Pitch
If you are creating or maintaining an app in the stores, keeping the app store information up to date can be a big task. When making small changes, for example, supporting a new app store region, you have to find and fill in the forms in an app store's dashboard. Then, after providing all the required information, that content still needs to be approved, where one word can be the difference between approval and rejection.
EAS Metadata aims to make this stage of app development as easy as possible. Using a simple configuration file, you can provide all the information to the stores without leaving your project environment. Combined with built-in validation, you get instant feedback about the content you provide, even before any review.
### Easy to configure, update, or maintain
You can start using EAS Metadata by [creating a new or generating a store config](./getting-started.mdx#create-the-store-config) from an existing app.
This store config lets you quickly update the app store information without leaving your project environment.
Before pushing the changes to the app stores, EAS Metadata looks for common pitfalls that might result in an app rejection.
### Faster feedback loop with validation
EAS Metadata comes with built-in validation, even before anything is sent to the app stores. This validation helps you iterate faster over the information without starting a review. Instead, you can begin the review process when everything is provided, and no issues are detected.
> Make sure to install the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme) to get auto-complete, suggestions, and warnings for **store.config.json** files.
### Extensible with dynamic store config
EAS Metadata also supports a more [dynamic store config](./config.mdx#dynamic-store-config), instead of only using JSON files.
This dynamic store config allows you to gather information from other places like external services.
With asynchronous functions, there are no limits to adapting EAS Metadata to suit your preferred workflow.
## Anti-pitch
Here are some reasons EAS Metadata might **not** be the right fit for a project.
### Does EAS Metadata support the Google Play Store?
We are committed to EAS Metadata and will expand functionality over time.
This also means that not all functionality is implemented in EAS Metadata.
The Google Play Store is one of those features currently not implemented.
See the [store config schema](./schema.mdx#config-schema) for all existing functionality.
### How do I use unsupported app store features?
EAS Metadata only sends the data from your store config to the app stores.
It does not block you from using the app store dashboards if you need a feature that EAS Metadata does not cover yet.
When using EAS Metadata and editing something in the app store dashboards, make sure to run `eas metadata:pull` after these changes. Without updating your local store config, EAS Metadata might overwrite your changes when pushing to the app stores.
### Using restricted app store accounts
You'll need to authenticate with the app store before EAS Metadata can access the information.
If you are working with a large corporate account, you might not have permission to use all functionality of EAS Metadata.
While you can use EAS Metadata in these cases, it's often more challenging due to the security restrictions.
# EAS Insights
## EAS Insights
An introduction to EAS Insights which is a preview service for projects using the expo-insights library.
> **info** EAS Insights is in preview and subject to breaking changes. While in preview, it is free to use.
**EAS Insights** is a service that will offer a view into a project's performance, usage, and reach. We are currently offering a preview of Insights that is available to all developers, and we will continue to roll out new features and functionality based on user feedback and suggestions.
EAS Insights makes it easy to see the state of your app, providing information about usage across platforms, app store versions, and timeframes.
## Integration with EAS Update
If you're already using [EAS Update](/eas-update/introduction/), we provide certain high-level usage insights without any additional client-side changes. This is possible because by aggregating data from client requests to check for an update into a limited Insights view that shows usage over time, as well as usage broken down by platform.
## Use the `expo-insights` library
Developers can add the `expo-insights` library to their projects and gain more precise usage metrics (than those provided by just aggregating update requests) and additional breakdowns by app store version. Currently, the library is limited to sending client events only pertaining to cold starts of the app, but in the future we will expand `expo-insights` to offer new types of events and payloads, to support more advanced functionality.
### Installation
To use the `expo-insights`, make sure your app is linked to your EAS project in **app.json** / **app.config.js** by running `eas init`, then install the library.
```sh
$ npm i -g eas-cli
$ eas init
$ npx expo install expo-insights
```
After installing the library, create a build either with [EAS](/build/setup/) or [locally](/guides/local-app-development/). The library will automatically send events to EAS Insights when the app is launched.
### View insights
To view insights data of your app, in the Expo dashboard, go to projects list, select your project and then select **Insights** from the navigation menu.
# Distribution
## Distribution: Overview
An overview of submitting an app to the app stores or with the internal distribution.
Get your app into the hands of users by submitting it to the app stores or with [Internal Distribution](/build/internal-distribution).
```sh
$ npm i -g eas-cli
$ eas build --auto-submit
$ eas submit
```
You can run `eas build --auto-submit` with [EAS CLI](/eas) to build your app and automatically upload the binary for distribution on the Google Play Store and Apple App Store.
This automatically manages **all native code signing** for Android and iOS for any React Native app. Advanced features such as payments, notifications, universal links, and iCloud can be automatically enabled based on your [config plugins](/config-plugins/introduction/) or native entitlements, meaning no more wrestling with slow portals to get libraries set up correctly.
### Get started
## App stores best practices
Learn about the best practices when submitting an app to the app stores.
This guide offers best practices for submitting your app to the app stores. To learn how to generate native binaries for submission, see [Create your first build](/build/setup/).
> **warning** **Disclaimer:** Review guidelines and rules are updated frequently, and enforcement of various rules can sometimes be inconsistent. There is no guarantee that your particular project will be accepted by either platform, and you are ultimately responsible for your app's behavior. That said, you can re-submit your app as needed to address feedback from reviews.
## Responsive design
It's a good idea to test your app on a device or simulator with a small screen (for example, an iPhone SE) and a large screen (for example, an iPhone X). Ensure your components render the way you expect, no buttons are blocked, and all text fields are accessible.
Try your app on tablets in addition to handsets. Even if you have `ios.supportsTablet: false` configured, your app will still render at phone resolution on iPads and must be usable.
> **warning** Apple may reject your app if elements don't render properly on an iPad, even if your app doesn't target the iPad form factor. Be sure and test your app on an iPad (or iPad simulator).
## Privacy policy
Starting October 3, 2018, all new iOS apps and app updates will be required to have a privacy policy to pass the App Store Review Guidelines.
### App privacy questions
Beginning December 8, 2020, new app submissions and updates are required to provide information about their privacy practices in App Store Connect. See [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/) for more information.
Apple will ask you a series of questions when you submit the app. Depending on which libraries you use, your answers may vary. For example, if you use `expo-updates`, you will need to say **Yes, we collect data from this app** and then you will want to select **Crash Data**.
## App transfers
An overview of transferring the ownership of an app to a different entity.
There are two different representations of your app to consider when handing over ownership to another entity: the app as it exists on Expo Application Services (to create builds with EAS Build, send updates with EAS Update, and so on) and the app records on the app stores (to distribute the app to end-users). The following guides explain how to handle app transfers in each case.
## Understanding app size
Learn about how to determine what the actual size of your app will be when distributed to users, and how to get insights into your app size and optimize it.
A common concern for developers is how much space their app takes up on the app store. This guide will help you:
- Understand what different build artifacts are used for
- Figure out the actual size of your app when distributed to users
- Get insights into your app size and optimize it
## Why is my app so big?
**It probably isn't, actually!** When examining the resulting artifact of a release build for an app, it's common for developers who are unfamiliar with native Android and iOS development to be surprised by the file size — it's usually much larger than they would expect for an app if they were to download it from an app store. **This is not the actual size of your app, which will be distributed on app stores!** When people talk about app sizes, they mean the size of the app that they will download to their device, not the size that will be uploaded to app stores or shared in development and testing.
There are various types of build artifacts that serve different purposes, and they are almost all larger than what users will see when they download your app from a store. This is because these builds are not optimized to target specific devices like they would when downloading from a store, but rather they typically include all of the code and resources that your app needs to run on a wide range of devices.
## Android apps
There are two types of Android build artifacts that you will interact with: APKs and AABs.
### `.apk` (Android Package)
When you build an APK with Gradle in a React Native project, the default behavior is to create a universal binary, which contains all the resources for all the different device types that your app supports. For example, it includes asset for every screen size, every CPU architecture, and every language, even though a single device will only need one of each. This means you can share this one file with anybody to install directly to their device, perhaps with [Orbit](https://expo.dev/orbit) or `adb` directly, and that will work.
Of course, if you're running an incredibly popular app store that serves millions of users, you don't want to send the same 50 MB file to every single user, especially if they're only going to use a fraction of the resources in the APK. This is why the Google Play Store and other app stores have a feature called "App Bundles" (Android) that allows you to upload a single binary and then the store will generate a custom binary for each user based on their device's needs.
### `.aab` (Android App Bundle)
On Android, all new apps submitted to the Play Store must be built as an [Android App Bundle (.aab)](https://developer.android.com/platform/technology/app-bundle). Once you have submitted the binaries to their respective stores, you will be able to see the download size for various device types.
### Determining Android app download and install size
Typically, what app developers care about the "download size" on the Play Store (what the users see in the store listing when they go to download the app). This will be the size of the APK that Google Play generates from your AAB, which is tailored to the user's device.
The only truly accurate way to see what your final app size will be shipped to users is to upload your app to the stores and download it on a physical device. Google Play also provides a reliable estimate for the expected download size on your developer dashboard. You can find this under the **App size** page in **Android vitals** on the [Google Play Developer Console](https://play.google.com/console/). For more information, see [Optimize your app’s size and stay within Google Play app size limits](https://support.google.com/googleplay/android-developer/answer/9859372?hl=en).
Note: Why did my APK size increase after upgrading to React Native 0.73?
---
React Native 0.73, which is used in Expo SDK 50, bumped the Android `minSdkVersion` to `23`. This had the side effect of changing the default value of [`extractNativeLibs`](https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs`) to `false`.
> If set to `false`, your native libraries are stored uncompressed in the APK. Although your APK might be larger, your application loads faster because the libraries load directly from the APK at runtime.
The following table shows that while the APK size increased, which may slightly impact download time for testers with [internal distribution](/build/internal-distribution/), the Google Play Store size remained the same.
| SDK | APK (debug variant) | APK (release variant) | AAB | Google Play |
| --- | ------------------- | --------------------- | ------- | ----------- |
| 49 | 66 MB | 27.6 MB | 28.2 MB | 11.7 MB |
| 50 | 168.1 MB | 62.1 MB | 27.4 MB | 11.7 MB |
If you would like to revert to the previous behavior, you can set `useLegacyPackaging` to `true` in your **gradle.properties** or by using [`expo-build-properties`](/versions/latest/sdk/build-properties/).
---
## iOS apps
The download size on the App Store of a minimal React Native app (created using the blank template) [is just under 4 MB](https://x.com/aleqsio/status/1844045829973344457).
There are two types of iOS build artifacts that you will interact with: APPs and IPAs.
### `.app` (iOS application bundle)
This is the actual application bundle for your app. When you download and install a build of your app into an iOS Simulator, you are downloading the `.app` bundle. These can either target specific architectures or be universal binaries. The size of your `.app` doesn't necessarily tell you too much about what the download size of your app will be on the store. You can't install a `.app` file directly to a physical iOS device.
### `.ipa` (iOS App Store Package)
IPA files are [ZIP](https://en.wikipedia.org/wiki/ZIP)) files that include the `.app` bundle and other resources that are needed to run the app on an iOS device. They are used for various types of distribution, including App Store, Ad Hoc, Enterprise, and TestFlight.
They include security and code signing information, such as the provisioning profile and entitlements. The App Store will process the IPA file and split it into smaller binaries for each device type, so the size of the IPA also does not represent the download size of your app.
### Determining iOS app download and install size
Typically, app developers care about the "download size" on the App Store (what the users see in the store listing when they go to download the app). This will be the size of the split IPA generated by the store from your universal IPA.
The only truly accurate way to see what your final app size will be shipped to users is to upload your app to the App Store and download it on a physical device. You can get accurate estimates from TestFlight: on [App Store Connect](https://appstoreconnect.apple.com/), navigate to TestFlight and select your build by clicking on the build number, then switch to the **Build Metadata** tab and click **App File Sizes**. You will see a list of estimated download and install sizes depending on the device type. Actually install sizes may also vary slightly depending on the iOS version of the device.
## Optimizing app size
As you add features to your app, you will add code, libraries, and assets, which may increase its size. If app size is important to you and your users, you may want to routinely review the size and optimize it. The following sections will help you understand what you can do to optimize several aspects of your app.
### Static assets
One of the most common sources of app size bloat is assets, such as fonts, icons, images, videos, and sounds. These can come from the assets that you import directly into your code, as well as JavaScript and native libraries. You won't be able to get a complete picture by reviewing your app assets directory.
Start by examining a build artifact to determine what assets are included in it.
- For Android, you can use [Android APK Analyzer](https://developer.android.com/studio/debug/apk-analyzer) or [apktool](https://apktool.org/) to inspect the contents of your app
- For iOS, rename an IPA file from `app.ipa` to `app.zip` and extract it to examine the contents, using the macOS utility `assetutil` to inspect **Assets.car**.
### JavaScript bundle size
To analyze JavaScript bundles, [use Expo Atlas](/guides/analyzing-bundles/). You may find libraries that you thought were very small actually have a large impact on the bundle, or that you forgot to remove a library after you stopped using it, and so on.
### Platform-specific optimizations
Independent of React Native and Expo, you can optimize your app for Android and iOS by using the following tools:
# Reference
## Webhooks
Learn how to configure webhooks to get alerts on EAS Build and Submit completion.
EAS can alert you as soon as your build or submission is completed via a webhook. Webhooks need to be configured per project. For example, if you want to be alerted for both `@johndoe/awesomeApp` and `@johndoe/coolApp`, in each directory, run the command:
```sh
$ eas webhook:create
```
After running it, you'll be prompted to choose the webhook event type (unless you provide the `--event BUILD|SUBMIT` parameter). Next, provide the webhook URL (or specify it with the `--url` flag) that handles HTTP POST requests. Additionally, you'll have to input a webhook signing secret, if you have not already provided it with the `--secret` flag. It must be at least 16 characters long, and it will be used to calculate the signature of the request body which we send as the value of the `expo-signature` HTTP header. You can use the [signature to verify a webhook request](#webhook-server) is genuine.
EAS calls your webhook using an HTTP POST request. All the data is passed in the request body. EAS sends the data as a JSON object. If the webhook responds with an HTTP status code outside of the 200-399 range, delivery will be attempted a few more times with exponential back-off.
Additionally, we send an `expo-signature` HTTP header with the hash signature of the payload. You can use this signature to verify the authenticity of the request. The signature is a hex-encoded HMAC-SHA1 digest of the request body, using your webhook secret as the HMAC key.
> If you want to test the above webhook locally, you can use a service such as [ngrok](https://ngrok.com/docs) to forward `localhost:8080` via a tunnel and make it publicly accessible with the URL `ngrok` gives you.
You can always change your webhook URL and/or webhook secret by running command:
```sh
$ eas webhook:update --id WEBHOOK_ID
```
You can find the webhook ID by running the command:
```sh
$ eas webhook:list
```
If you want us to stop sending requests to your webhook, run the command below and choose the webhook from the list:
```sh
$ eas webhook:delete
```
## Webhook payload
Note: Build webhook payload
---
The build webhook payload may look as the example below:
```json
{
"id": "147a3212-49fd-446f-b4e3-a6519acf264a",
"accountName": "dsokal",
"projectName": "example",
"buildDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/147a3212-49fd-446f-b4e3-a6519acf264a",
"parentBuildId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for build retries
"appId": "bc0a82de-65a5-4497-ad86-54ff1f53edf7",
"initiatingUserId": "d1041496-1a59-423a-8caf-479bb978203a",
"cancelingUserId": null, // available for canceled builds
"platform": "android", // or "ios"
"status": "errored", // or: "finished", "canceled"
"artifacts": {
"buildUrl": "https://expo.dev/artifacts/eas/wyodu9tua2ZuKKiaJ1Nbkn.aab", // available for successful builds
"logsS3KeyPrefix": "production/f9609423-5072-4ea2-a0a5-c345eedf2c2a"
},
"metadata": {
"appName": "example",
"username": "dsokal",
"workflow": "managed",
"appVersion": "1.0.2",
"appBuildVersion": "123",
"cliVersion": "0.37.0",
"sdkVersion": "41.0.0",
"buildProfile": "production",
"distribution": "store",
"appIdentifier": "com.expo.example",
"gitCommitHash": "564b61ebdd403d28b5dc616a12ce160b91585b5b",
"gitCommitMessage": "Add home screen",
"runtimeVersion": "1.0.2",
"channel": "default", // available for EAS Update
"releaseChannel": "default", // available for legacy updates
"reactNativeVersion": "0.60.0",
"trackingContext": {
"platform": "android",
"account_id": "7c34cbf1-efd4-4964-84a1-c13ed297aaf9",
"dev_client": false,
"project_id": "bc0a82de-65a5-4497-ad86-54ff1f53edf7",
"tracking_id": "a3fdefa7-d129-42f2-9432-912050ab0f10",
"project_type": "managed",
"dev_client_version": "0.6.2"
},
"credentialsSource": "remote",
"isGitWorkingTreeDirty": false,
"message": "release build", // message attached to the build
"runFromCI": false
},
"metrics": {
"memory": 895070208,
"buildEndTimestamp": 1637747861168,
"totalDiskReadBytes": 692224,
"buildStartTimestamp": 1637747834445,
"totalDiskWriteBytes": 14409728,
"cpuActiveMilliseconds": 12117.540078,
"buildEnqueuedTimestamp": 1637747792476,
"totalNetworkEgressBytes": 355352,
"totalNetworkIngressBytes": 78781667
},
// available for failed builds
"error": {
"message": "Unknown error. Please see logs.",
"errorCode": "UNKNOWN_ERROR"
},
"createdAt": "2021-11-24T09:53:01.155Z",
"enqueuedAt": "2021-11-24T09:53:01.155Z",
"provisioningStartedAt": "2021-11-24T09:54:01.155Z",
"workerStartedAt": "2021-11-24T09:54:11.155Z",
"completedAt": "2021-11-24T09:57:42.715Z",
"updatedAt": "2021-11-24T09:57:42.715Z",
"expirationDate": "2021-12-24T09:53:01.155Z",
"priority": "high", // or: "normal", "low"
"resourceClass": "android-n2-1.3-12",
"actualResourceClass": "android-n2-1.3-12",
"maxRetryTimeMinutes": 3600 // max retry time for failed/canceled builds
}
```
---
Note: Submit webhook payload
---
The submit webhook payload may look as the example below:
```json
{
"id": "0374430d-7776-44ad-be7d-8513629adc54",
"accountName": "dsokal",
"projectName": "example",
"submissionDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/0374430d-7776-44ad-be7d-8513629adc54",
"parentSubmissionId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for submission retries
"appId": "23c0e405-d282-4399-b280-5689c3e1ea85",
"archiveUrl": "http://archive.url/abc.apk",
"initiatingUserId": "7bee4c21-3eaa-4011-a0fd-3678b6537f47",
"cancelingUserId": null, // available for canceled submissions
"turtleBuildId": "8c84111e-6d39-449c-9895-071d85fd3e61", // available when submitting a build from EAS
"platform": "android", // or "ios"
"status": "errored", // or: "finished", "canceled"
"submissionInfo": {
// available for failed submissions
"error": {
"message": "Android version code needs to be updated",
"errorCode": "SUBMISSION_SERVICE_ANDROID_OLD_VERSION_CODE_ERROR"
},
"logsUrl": "https://submission-service-logs.s3-us-west-1.amazonaws.com/production/submission_728aa20b-f7a9-4da7-9b64-39911d427b19.txt"
},
"createdAt": "2021-11-24T10:15:32.822Z",
"updatedAt": "2021-11-24T10:17:32.822Z",
"completedAt": "2021-11-24T10:17:32.822Z",
"maxRetryTimeMinutes": 3600 // max retry time for failed/canceled submissions
}
```
---
## Webhook server
Here's an example of how you can implement your server:
```js server.js
const crypto = require('crypto');
const express = require('express');
const bodyParser = require('body-parser');
const safeCompare = require('safe-compare');
const app = express();
app.use(bodyParser.text({ type: '*/*' }));
app.post('/webhook', (req, res) => {
const expoSignature = req.headers['expo-signature'];
// process.env.SECRET_WEBHOOK_KEY has to match SECRET value set with `eas webhook:create` command
const hmac = crypto.createHmac('sha1', process.env.SECRET_WEBHOOK_KEY);
hmac.update(req.body);
const hash = `sha1=${hmac.digest('hex')}`;
if (!safeCompare(expoSignature, hash)) {
res.status(500).send("Signatures didn't match!");
} else {
// Do something here. For example, send a notification to Slack!
// console.log(req.body);
res.send('OK!');
}
});
app.listen(8080, () => console.log('Listening on port 8080'));
```
# Expo accounts
## Account types
Learn about the different types of Expo accounts and how to use them.
An Expo account is a container that holds Expo projects and allows for different amounts of collaboration. There are two types of Expo accounts: **Personal**, and **Organization**.
The type of account you choose to put a new project depends on the nature of the project. If you are looking to collaborate or set up a workflow for your development team, always create an Organization account. For personal or hobby projects, a Personal account is sufficient.
## Personal accounts
When you [sign up for an account](https://expo.dev/signup) with Expo, a Personal account is automatically created for you. This account is a good place to work on your personal projects.
> **warning** Do not share authentication credentials for your Personal account with anyone for any reason.
## Organizations
An Organization account is best used to hold projects that you wish to share with other members of a company or a group of developers. It serves as a shared container where your team can collaborate on one or multiple projects and have access to shared credentials.
You can invite other members to your Organization account, and then give these members different roles that grant a level of access within the organization. For more information, see [role privileges in Manage access](#manage-access).
Creating an organization account is useful when:
- You think you may need to transfer control of that Organization's projects in the future.
- Sharing one or multiple projects with a team of collaborators.
- More than one [Owner](/accounts/account-types/#manage-access) needs to be assigned.
- Expenses need to be isolated.
- Granting different levels of access by assigning a role to each member of the organization.
- Structuring projects for different contexts. For example, when working for different clients, a new organization may be created for each client.
- Sharing an [EAS Subscription](/eas/).
### Create a new Organization
If you are logged in to your Personal account, you can create a new Organization from the dashboard:
- Select your account's username in the navigation menu to open the dropdown menu.
- Select **Create Organization** under Organizations in the dropdown menu.
- Add a name for your Organization and select the **Create** button.
After creating a new Organization, you are redirected to the new dashboard page for the organization. To associate a new project with the Organization, you have to add the [`owner` key](/versions/latest/config/app/#owner) under the `expo` key to your project's **app.json**.
### Convert a Personal account into an Organization
You can convert your Personal account into an Organization when you want to share access to projects with other members and assign each member a role-based privilege.
From the **User settings** of your Personal account, go to [Convert your account into an organization](https://expo.dev/settings#convert-account) section to start the process.
When you are going through this process, we take a lot of care to make sure that all of the functionality that you and your users rely on will continue to work as expected:
- You can continue to deliver updates and push notifications to your users.
- You can still use any Android or iOS credentials stored on Expo's servers.
- Any integrations using your personal access token or webhooks will continue to operate and are transferred to the new designated owner.
- Your EAS subscription will continue without interruption.
- Your production apps will continue to operate without interruption.
### Invite a member
Other Expo users can be invited to join your Organization. To invite a new member:
- Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the Expo dashboard.
- Click the button **Invite**. This will open a form to invite a member to the organization.
- In the form, enter the email of the user you want to invite and select the role they should have upon joining the organization. For more information, see [role privileges in Manage access](#manage-access).
When inviting a new member, keep in mind:
- Only members with an Owner or an Admin role can invite others.
- Members with an Owner role can grant members and invitees any role.
- Members with an Admin role can only give members and invitees up to and including Admin role (every role but Owner).
### Change the role of a member
To change the role privileges of a member, make sure you have either an [**Owner** or **Admin** role](/accounts/account-types/#manage-access) and follow the steps below:
- Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the Expo dashboard.
- Next to the member whose role you want to change, click on the three-dotted menu icon and change the role.
### Remove a member
To remove a member, make sure you have either an [**Owner** or **Admin** role](/accounts/account-types/#manage-access) and follow the steps below:
- Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the Expo dashboard.
- Next to the member you want to remove, click on the three-dotted menu icon.
- Click **Remove member**.
### Rename an account
Accounts can be renamed a limited number of times. Only Owners can rename accounts. To rename an account, visit **Organization settings** > [**Overview**](https://expo.dev/accounts/[account]/settings) and follow the steps under [**Rename account**](https://expo.dev/accounts/[account]/settings#rename-account).
### Transfer projects between accounts
Projects can be transferred a limited number of times. A user must be an Owner or Admin on both source and destination accounts to transfer projects between them. Visit **Project** > **Configuration** > [**Project settings**](https://expo.dev/accounts/[account]/projects/[project]/settings) and follow the steps under **Transfer project**.
#### Caveats
> If you want to transfer the ownership of a project from your Personal or Organization account (source) to another person or company (destination), and you are not allowed "Owner" or "Admin" permissions on the destination account, you can create an escrow account (a new Organization account). This solves the problem that a user must be an "Owner" on the source account and either an "Owner" or "Admin" on the destination account to transfer projects between them. Once the escrow account is created, you can grant the ultimate destination account member the Owner role on the escrow account and safely transfer the project to the escrow account. The receiving person or company can then transfer it to their destination account from the escrow account without having had access to the destination account itself.
### Manage access
Access for members is managed through a role-based system. Users can have the _owner_, _admin_, _developer_, or _viewer_ roles within an Organization account.
| Role | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Owner** | Can take any action on an account or any projects, including deleting them. |
| **Admin** | Can control most settings on your account, including signing up for paid services, changing permissions of other users, and managing programmatic access. |
| **Developer** | Can create new projects, make new builds, release updates, and manage credentials. |
| **Viewer** | Can only view your projects through Expo Go but cannot modify your projects in any way. |
### Security activity
Security activity is a list of changes that happened to an account's profile. It includes changes to password, email, and 2FA authentication setup, among others.
It can be found under **Overview** > [**User settings**](https://expo.dev/settings).
## Two-factor authentication
Learn about how you leverage two-factor authentication (2FA) to secure your Expo account.
Two-factor authentication provides an extra layer of security when logging in to expo.dev, the Expo Go app, and command line tools. With two-factor authentication enabled, you will need to provide a short-lived code in addition to your username and password to access your account.
## Enable two-factor authentication (2FA)
You can enable two-factor authentication from your [personal account settings](https://expo.dev/settings#two-factor-auth).
## Two-factor authentication methods
You can receive 2FA codes through an authenticator app.
### Authenticator apps
Expo accepts any authenticator app that supports Time-based One-time Passwords (TOTP) including:
- [Last Pass Authenticator](https://lastpass.com/auth/)
- [Authy](https://authy.com/)
- [1Password](https://support.1password.com/one-time-passwords/)
- [Google Authenticator](https://support.google.com/accounts/answer/1066447)
- [Microsoft Authenticator](https://www.microsoft.com/en-us/account/authenticator)
Expo will provide a QR code to scan with your authenticator app during setup. The app will provide a confirmation code to enter on Expo. Enter the code to finish activating 2FA via your authenticator app.
### SMS messages
> **warning** **Deprecated:** SMS is no longer supported for newly-added two-factor authentication methods. Existing SMS two-factor authentication methods will continue to work, though we suggest switching to an authenticator app as it provides better security.
Provide a mobile phone number to receive a short-lived token via SMS. Codes received via SMS will be valid for at least 10 minutes, so you may receive the same code multiple times within this window. If you set an SMS device as your default 2FA method, you will be sent a verification code automatically whenever you take an action that requires a 2FA code.
### Recovery codes
When you set up two-factor authentication for your account, you'll receive a set of recovery codes. These codes can be used instead of a one-time password if you lose access to your authenticator app or SMS device. Keep in mind that each recovery code is only valid for one use.
If you selected the option to download your recovery codes at the time they were created, you can locate them in a file labeled as **expo-recovery-codes.txt**.
> Store your recovery codes in a secure and memorable place to ensure you, and only you can access your account!
## Change your two-factor settings
You can make changes to your two-factor settings from your [personal account settings](https://expo.dev/settings). You can:
- add or remove authentication methods
- set your default method
- regenerate your recovery codes
- disable two-factor authentication for your account
You will need to provide a one-time password to make any changes to your 2FA settings.
## Recover your account
### Recovery codes
When you set up your account to use 2FA, Expo provides you with a list of recovery codes. In the event you lose your device(s), a recovery code may be used in place of a one-time password. Each of these codes may only be used once. You may regenerate your recovery codes, which will invalidate any existing codes, from your [personal account settings](https://expo.dev/settings/).
### Secondary 2FA methods
By setting up multiple authentication methods associated with different physical devices, you can ensure you will not lose access to your account in the event a device is reset or lost.
### Manual recovery
If you cannot access your account through any of the supplied methods, you may email Expo support from the email associated with your account. Unfortunately, we cannot guarantee we will be able to restore your access to your account in this scenario.
## Programmatic access
Learn about types of access tokens and how to use them.
When setting up CI or writing a script to help manage your projects, we recommend avoiding using your username and password to authenticate. With these credentials, anyone will be able to log in and use your account.
Instead of providing credentials, you can generate tokens that will allow you to manage each integration point separately. Anyone who has access to these tokens will be able to perform actions against your account. Treat them with the same care as a user password. In case something is leaked, you can revoke these tokens to block access.
## Personal access tokens
You can create Personal access tokens from the [Access tokens](https://expo.dev/settings/access-tokens) on your dashboard. Anyone with this token can perform actions on your behalf. That applies to all content on your Personal Account, as well as any Personal Accounts or Organizations that you have been granted access to.
## Robot users and access tokens
Accounts can create Robot users to take actions on resources owned by the Account. Bot Users can be assigned [a role](/accounts/account-types/#manage-access) to limit the actions they are authorized to perform. Bot users cannot sign in to any Expo products, cannot own any projects themselves, and can only authenticate via an access token.
## Access tokens usage
You can use any tokens you have created to perform actions with the EAS CLI. To use tokens, you need to define an environment variable, like `EXPO_TOKEN="token"`, before running commands.
Once you set the `EXPO_TOKEN` environment variable, you can run any EAS CLI command authenticated with the token without running the `eas login` command. The `eas login` command is only used for username and password authentication. The `EXPO_TOKEN` auth method takes precedence over the username and password if both are configured.
For example, once you obtain a token, you can run the following EAS CLI command to trigger a build:
```sh
EXPO_TOKEN=my_token eas build
```
If you are using GitHub Actions, [you can configure the `token` property](https://github.com/expo/expo-github-action#configuration-options) to include this environment variable in all the job steps.
Common situations where access tokens are useful:
- Publish or build from CI without providing your Expo username and password
- Renew a token to keep it as secure as possible; no need to reset your password and sign out of all sessions
- Give someone (or a script) one-time access to your project with limited permissions
## Revoke access tokens
In case a token is accidentally leaked, you can revoke it without changing your username and password. When you revoke the access token, you block all access to your account using this token. To do this, go to the [Access Token page](https://expo.dev/settings/access-tokens) on your dashboard and delete the token you want to revoke.
## Single Sign-On (SSO)
Learn how your enterprise organization can use your identity provider to manage Expo users on your team.
Single Sign-On (SSO) is available for [Enterprise plan](https://expo.dev/pricing) customers.
To get started, prepare your identity provider (IdP) for Expo SSO and gather information by following the [configuration guide for your IdP](/#identity-provider-support) below. Once you have done this, an owner of your Organization can follow instructions to [enable SSO](#enable-sso).
If you have questions or issues, [contact us](https://expo.dev/contact) and we'll help you set up your organization.
## Identity provider support
Expo SSO supports the following identity providers:
| Identity providers | Resources |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| [Okta](https://www.okta.com/) | [Configuration guide](https://expo.fyi/sso-setup-okta) |
| [OneLogin](https://www.onelogin.com/) | [Configuration guide](https://expo.fyi/sso-setup-onelogin) |
| [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/microsoft-entra) | [Configuration guide](https://expo.fyi/sso-setup-microsoft) |
| [Google Workspace](https://www.google.com/) | [Configuration guide](https://expo.fyi/sso-setup-google-ws) |
We implement the [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) specification and are working to verify additional compatible identity providers. If you use another identity provider and are interested in SSO, [let us know](https://expo.dev/contact).
## Setting up SSO on an organization
Step 1:
Log in as the Organization account owner, select the Organization, then go to **Organization settings** > **Overview**.
Step 2:
Click the **Start** button next to the **Create SSO configuration for account** option.
Step 3:
Enter the configuration details for your IdP using the information you collected during the IdP setup:
- Client ID
- Client secret
- IdP subdomain/tenant ID, if needed. Click the **?** icon above the Issuer field for help with what to enter.
Step 4:
Click **Create SSO Configuration**.
Step 5:
The **Organization settings** > **Overview** page will now display an **Update SSO configuration** option. Use this option to update the client secret if it changes.
## SSO user sign in
### Expo website
Step 1:
Navigate to [expo.dev/sso-login](https://expo.dev/sso-login) and enter the account name of your organization. You can create a link that pre-fills the organization name. For example, [expo.dev/sso-login/test-org](https://expo.dev/sso-login/test-org) pre-fills `test-org`.
Step 2:
Log in to your identity provider (IdP).
Step 3:
You'll be prompted to select an Expo username. This will be the username for your Expo account.
### Expo CLI
> Available with SDK 50 and above. For previous versions, the Expo CLI will use your SSO account after logging in via the EAS CLI.
When using the Expo CLI, you can run the following command to log in to your Expo account.
```sh
$ npx expo login --sso
```
You will be prompted to log in via the Expo website in a browser and will be redirected back to the CLI upon completion.
### EAS CLI
When using the EAS CLI, you can run the following command to log in to your Expo account.
```sh
$ eas login --sso
```
You will be prompted to log in via the Expo website in a browser and will be redirected back to the CLI upon completion.
### Expo Go
Step 1:
Click the **Continue with SSO** button on the sign-in page when going through the sign-in flow.
Step 2:
Follow the [above steps](#expo-website) to sign in to the Expo website.
## SSO user restrictions
SSO users are like regular users. However, there are a few known exceptions:
- SSO users can only belong to their SSO organization. They also cannot create additional organizations.
- SSO users cannot leave their SSO organization. Doing so deletes their SSO user.
- SSO users cannot log in to the Expo forums.
- SSO users cannot subscribe to EAS for their personal accounts.
## SSO administration
Both new organizations and existing organizations can enable SSO as a sign in option. Organizations with existing non-SSO members can enable SSO and then direct new members to the SSO sign-in page, while existing users continue to use their current Expo credentials. To support external contributors, SSO-enabled organizations also allow inviting additional non-SSO users via email.
### Transitioning existing users to SSO
Regular users may be a member of one or many personal, team, and organization accounts while SSO users belong exclusively to their organization account. Thus, existing users cannot be directly converted into SSO users. However, a regular user who's already a member of your organization may create a second user by going to the [SSO login page](https://expo.dev/sso-login). Then, their regular user can be removed from the organization.
To transition from using a regular Expo account to an SSO account, follow these steps:
Step 1:
Check if you're already logged in at [expo.dev](https://expo.dev). If so, log out.
Step 2:
Go to the [SSO login page](https://expo.dev/sso-login) and follow the prompts, such as entering your organization name, creating a new Expo username, and logging in to your identity provider.
Step 3:
By default, your new SSO user will have the View Only role. If you need a different role, ask an Admin or Owner to update your role in [**Member**](https://expo.dev/accounts/[account]/settings/members) settings.
Step 4:
Run `eas login --sso` to switch to your new account on the CLI.
Step 5:
At this time, the Admin or Owner can remove your old user from the organization. In [**Member**](https://expo.dev/accounts/[account]/settings/members) settings, the list of organization members indicates whether a user is an SSO or non-SSO user. The Admin or Owner can click the dropdown next to the old user and click **Remove member**.
Step 6:
If you no longer need your old user account, log out of your new SSO account, then log in to your old account and go to [**User settings**](https://expo.dev/settings). Scroll down and click **Delete Account**. **Note that this will delete any projects under your old user account.** It will not affect any projects owned by the organization.
> If you wish to reuse your old username on your new SSO user account, you can go to [**User settings**](https://expo.dev/settings) under your old user and rename it before creating your SSO account. Alternatively, you can rename your SSO user account's Expo username after deleting your old user. While Expo usernames need to be unique, it is OK if your email address on your identity provider matches the email address of your old user.
### Remove SSO users
If someone has left your organization, remove or disable them in your IdP. Depending on the token refresh duration you configured with your IdP, the removed user will subsequently lose access to their Expo account.
If you wish to remove them ahead of that time or you wish to remove them to clean up users on your account, you may do so on the organization **Member** settings page:
Step 1:
Navigate to your [organization account **Member** settings](https://expo.dev/accounts/[account]/settings/members).
Click the dropdown next to the member you wish to delete, and click **Delete SSO user**.
> **warning** This will delete their personal account and all data associated with it. All data in your organization account will remain unaffected.
### Change billing or discontinue use of SSO
An active Enterprise Plan is required to continue using SSO. [Contact us](https://expo.dev/contact) if you wish to discontinue the use of SSO or change your plan.
To ensure uninterrupted access to your organization whether or not SSO is enabled, SSO organizations must keep at least one non-SSO user with the Owner role as a member.
### Delete SSO organization
Once SSO is configured for an organization, account deletion must be done manually by the Expo team. [Contact us](https://expo.dev/contact) for assistance.
## Audit logs
Learn how to track and analyze your account's activities by using the audit logs.
> **info** Audit logs are available for [Enterprise plan](https://expo.dev/pricing) customers.
Audit logs record actions made with Expo Application Services (EAS) by accounts. Recorded data includes information about the affected entities, the type of modification made to them, who performed the action, and when the activity occurred.
## Key points
- Audit logs can only be created and never modified or deleted, they serve as a source of truth to help monitor events and debug issues occurring within accounts.
- **Audit logs are available to Enterprise plan customers**. When subscribed, some of the logs used internally by Expo are immediately available, while other types of logs are starting to be collected after the subscription is activated.
- Audit logs are stored for 1.5 years. If an account is deleted, its audit logs will be deleted after 90 days.
- To access them, go to **Account settings** > [**Audit logs**](https://expo.dev/accounts/[account]/settings/audit-logs).
## Use cases
### Permission monitoring
Audit logs can track user invitations and permission changes within your organization. An example security event could include a compromised employee account that invites an attacker into an organization and changes their permission to [Admin](/accounts/account-types/#manage-access).
In this scenario, audit logs would record which employee account invited the attacker and modified permissions. Since audit logs are immutable, the attacker would not be able to delete this recorded history. Other organization members will be able to review the audit logs to determine which account was compromised, take action to revoke the attacker's permissions and secure the employee's account.
### Access history
An Expo organization account can include many projects where development access is controlled by distribution certificates assigned to individual teams. When devices are granted to join these teams, it is important to track when access is granted and removed for historical record keeping. While a device may not currently be included in an Apple team, it may be useful to see who previously had access to the team in the event of an internal security incident.
The Apple devices listed within the Expo team's settings will only show devices that are currently registered to an account, but with the creation of audit logs, historical modifications of Apple teams and devices can be viewed.
## Audit log entities
While we are working on adding more entities in future, the following entities are already enabled:
- Accounts
- Android App Credentials
- Android Keystore
- Apple devices
- Apple Distribution Certificate
- Apple Provisioning Profile
- Apple Team
- App Store Connect API key
- Google Service Account key
- iOS App Credentials
- Project
- User Invitations
- User Permissions
### Structure
Audit log entries include the following fields:
| Field | Description |
| ------------- | ---------------------------------------------------------------------------------------------- |
| Actor | The account actor that performed the particular action. |
| Entity Type | The object that was modified with one of the modification types: `CREATE`, `UPDATE`, `DELETE`. |
| Mutation Type | The type of modification: `CREATE`, `UPDATE`, `DELETE`. |
| Created At | When the particular action was performed. |
Additionally, clicking on an Audit log row, you can view the metadata relevant to that log.
## Export
- **Audit logs are available to Enterprise plan customers**. When subscribed, some of the logs used internally by Expo are immediately available, while other types of logs will be collected after the subscription is activated.
Export is available with a time range of up to 30 days. The exported file will include all the fields shown on the Audit logs page except for the **Message** field.
# Billing
## Billing: Overview
An overview of information on billing and subscriptions to manage your EAS account's plans, invoices, receipts, payments, and usage.
Expo provides various subscription plans for integrated cloud services through Expo Application Services (EAS). You can manage and track invoices, payments, plans, and other billing-related information on the **Billing and Receipts** pages in your account's dashboard. Only account Owners and Admins have access to this page.
See our list of resources below to learn more about different aspects of billing and subscriptions:
## Plans
## Manage billing
## Usage-based pricing
## Frequently Asked Questions (FAQs)
## Subscriptions, plans, and add-ons
In-depth guide on available Expo Application Services (EAS) plans and how they work, usage-based pricing and add-ons.
[Expo Application Services (EAS)](/eas/) offers [free access](https://expo.dev/eas/fair-use#commercial-usage) to a limited quantity of low-priority builds on [EAS Build](/build/introduction/) and free updates with [EAS Update](/eas-update/introduction/). These limits reset monthly.
Beyond the Free plan, there are different subscription plans to cater to various customers and their needs. Each plan offers credits to enable priority builds for EAS Build and broader access to EAS Update through more monthly active users and extra bandwidth. We also offer add-ons that complement subscriptions and enable opt-in features to amplify customer needs.
This page lists different subscription-based plans and available add-ons.
## Subscriptions
Subscriptions are billed monthly and are priced the same worldwide (pre-tax). To see your account's current subscribed plan, go to [your account's Billing](https://expo.dev/settings/billing), and under **Plan**, you will find details of your current plan.
You can also cancel a subscription at any time. See [Cancel a plan](/billing/manage/#cancel-a-plan) for more information.
We also offer annual contracts on an as-needed basis. Contact our [customer support](https://expo.dev/contact) team to see if an annual plan suits you.
## Plans
Each plan has specific limits. However, subscribers can exceed them and pay for additional usage with [usage-based billing](/billing/usage-based-pricing/).
### Production
The Production plan is designed for professional developers and small businesses. Subscribing to this plan gets you access to:
- Reliable, production-grade services
- Monthly [credits](/billing/usage-based-pricing/) for high-priority builds for EAS Build
- More unique users, higher bandwidth, and storage limit for EAS Update
If you exceed the limits or use up your monthly credits, any further usage will be charged at [usage-based prices](/billing/usage-based-pricing/).
### Enterprise
The Enterprise plan is designed for organizations and enterprises that have large projects and require additional resources such as dedicated support. Subscribing to this plan gets you access to:
- Reliable, production-grade services
- Monthly [credits](/billing/usage-based-pricing/) for high-priority builds for EAS Build
- More unique users, higher bandwidth, and storage limit for EAS Update
An Enterprise plan offers much higher monthly credits for EAS Build and bandwidth for EAS Update than any other plan.
If you exceed the limits or use up your monthly credits, any further usage will be charged at [usage-based prices](/billing/usage-based-pricing/).
### On-demand
The On-demand plan is a pay-as-you-go option for developers who have variable usage of our services. You can dial up or down your usage as you see fit and upgrade to the Production or Enterprise plan if needed.
Subscribing to the On-demand plan gets you access to:
- Reliable, production-grade services
- Priority builds with EAS Build at usage-based prices
- The ability to exceed the limits of the Free plan for EAS Update and pay for additional usage
## Usage-based billing
Usage-based billing is applied to customers who exceed their plan limits. It enables you to use our services without worrying about limitations or any contractual obligations.
Usage-based billing is billed monthly and is currently enabled for EAS Build and EAS Update. We provide an estimate of your existing usage and any overage charges on [your account's Billing](https://expo.dev/settings/billing).
## Add-ons
### Enterprise Support
The Enterprise Support add-on is only available for new Enterprise plan subscribers, subject to availability. Key features include:
- Receiving professional, long-term support from our experts
- Direct communication channel support with a Service-Level Agreement (SLA)
- A dedicated account manager
## Manage plans and billing
Learn how to update, downgrade, or cancel your Expo account's plans and manage billing details.
**Billing** in the Expo dashboard provides information about your account's currently subscribed plan and monthly usage. It also allows you to manage your plan and billing details.
This guide explains how to manage your account's plans and billing information.
## Manage plans
### View the current plan
For For Organization accounts:
- Click [Billing](https://expo.dev/settings/billing) from the navigation menu under **Organization settings**.
- Under **Plan**, you can see the current plan for your account.
For example, an Organization account is subscribed to the Production plan below:
For For Personal accounts:
- Click [Billing](https://expo.dev/settings/billing) from the navigation menu under **Account settings**.
- Under **Plan**, you can see the current plan for your account.
For example, a Personal account is subscribed to the Production plan below:
### Upgrade to a new plan
To upgrade to a different plan:
- Click [Billing](https://expo.dev/settings/billing) from the navigation menu in Expo dashboard.
- Under **Plan**, click **Change Plan** if you are already on a paid plan. If you are on the Free plan, click **Subscribe**. It opens the **Cart** page.
- Under **Choose a plan**, you can see a list of all available plans. Choose the plan you want to upgrade to and click the **Continue to Review** button at the bottom of this page.
- Next, you are asked to review and confirm your new plan. After reviewing the details on this page, enable the checkbox and click **Continue to Checkout** to open the checkout page.
- On **Checkout**, you are asked to enter your email, card details, and billing address. After adding these details, click **Pay Now** to subscribe to the new plan.
> **Note**: If you are subscribing to the [On-demand plan](/billing/plans/#on-demand), you are not charged any fee when you check out. Since this is a usage-based plan, all charges are due at the end of the billing period.
### Downgrade a plan
If you are on a Production, Enterprise, or Legacy plan, you can downgrade to the On-demand or Free plan.
For To On-demand plan:
Downgrading to the On-demand plan takes effect after your current billing period ends.
To downgrade, go to [Billing](https://expo.dev/settings/billing) and follow the steps below:
- Under **Plan**, click **Change Plan**.
- Under **Choose a plan**, select **On-demand plan**. Then, click **Continue to Review** at the bottom of the page.
- Next, you need to review and confirm that you're switching to the On-demand plan. Verify that the account you're downgrading from is correct (see the _warning_ on this page). After reviewing the details, enable the checkbox and click **Continue to Checkout**.
- A **Checkout** dialog opens up where you can confirm all the details. Click **Schedule Downgrade** to schedule a downgrade of your plan.
- After confirming your account for a plan downgrade to On-demand, the same information is also reflected under **Billing** > **Plan**.
For To Free plan:
Downgrading to the Free plan takes effect after your current billing period ends and is equivalent to canceling a paid plan.
To downgrade, go to [Billing](https://expo.dev/settings/billing) and follow the steps below:
- Under **Plan**, click **Change Plan**.
- Under **Choose a plan**, select **Free plan**. Then, click **Continue to Review** at the bottom of the page.
- Next, you need to review and confirm that you're switching to the Free plan. Verify that the account you're downgrading is correct (see the _warning_ on this page). After reviewing the details, enable the checkbox and click **Cancel Paid Plan**.
- You will be redirected to Stripe's cancel plan screen, where you can cancel your plan. Switching to the Free plan is equivalent to canceling a paid plan at the end of the billing period.
### Cancel a plan
For From Production or Enterprise plan:
Cancellation from a Production or Enterprise plan takes effect after your current billing period ends.
To cancel your plan, on [Billing](https://expo.dev/settings/billing), click **Cancel Plan** and then click **Continue to Stripe** to follow the process of your current plan's cancellation.
For From On-demand to Free plan:
Cancellation for the On-demand plan takes effect immediately and resets your account's quota to the Free plan.
To cancel your plan, on [Billing](https://expo.dev/settings/billing), click **Cancel Plan** and then click **Cancel Immediately**.
> **Note**: If you unsubscribe from an **On-demand plan,** you will be charged for any usage incurred during the current billing period.
## Manage billing information
You can manage your billing-related details such as name, email, address, and payment information, or add a tax ID. All of this information is mentioned on the [monthly invoice](/billing/invoices-and-receipts/) you receive for the subscribed plan.
### Update Billing name, email, or address
To update your billing name, email, or address:
- On [Billing](https://expo.dev/settings/billing), click **Update billing information**. This will open Stripe's portal where you can view payment methods, billing information, invoicing history, and update your billing information. Then, click **Update information**.
- Update your billing details by entering your new name, email or address, then click **Save**.
### Tax ID
To add or update your billing tax ID:
- On [Billing](https://expo.dev/settings/billing), click **Update billing information**. This will open Stripe's portal where you can view and update your billing information. Then, click **Update information**.
- Under **Tax ID**, select the ID type, enter your valid tax ID, and click **Save**.
### Payment method
To add a new payment method information:
- On [Billing](https://expo.dev/settings/billing), click **Update billing information**. This will open Stripe's portal where you can view and update your billing information.
- Under **Payment method**, click on **Add payment method** to add a new payment method.
- Enter your new payment method details and click **Add**.
## View payment history, invoices, and receipts
Learn how to view your account's payment history, download invoices and receipts, and request a refund for a charge.
**Receipts** in the Expo dashboard provide information about an account's payment history and access to invoices and receipts. It also provides information on payment dates, payment status, and the total amount for that payment. You can also request a refund for a charge if you believe it has been made in error.
> **Note**: You can only access the **Receipts** if you have [Owner or Admin access](/accounts/account-types/#manage-access) to your account.
## Receipts
To view your account's payment history, click [Receipts](https://expo.dev/settings/receipts) in the navigation menu under **Account settings** or **Organization settings**.
For example, an [Organization account's](/accounts/account-types/#organizations) receipts are shown below:
### Download and view an invoice
To download and view an invoice for a billing period, go to [Receipts](https://expo.dev/settings/receipts) and:
- Click the **Date** for the billing period corresponding to the invoice. You will be navigated to a page hosted by Stripe. As an example, the March 22, 2024 invoice below links to this page:
- Click **Download invoice**. You will receive a PDF copy of the invoice.
### Download and view a receipt
To download and view a receipt for a billing period, go to [Receipts](https://expo.dev/settings/receipts) and:
- Click the **Date** for the billing period corresponding to the receipt. You will be navigated to the appropriate receipt hosted by Stripe. As an example, the March 22, 2024 receipt below links to this page:
- Click on **Download receipt.** You will receive a PDF copy of the receipt.
### Request a refund
You can request a refund directly from the [Receipts](https://expo.dev/settings/receipts) page. The approval process is manual and our team investigates any errors before providing a refund.
To request a refund:
- Next to a receipt, click the three-dot menu and then click **Request Refund:**
- Fill the **Request a refund** form with details for the refund and click **Continue**:
- Our billing team receives and reviews refund requests. Once a refund is approved, the amount is credited to your payment method. Refunds typically take 5 to 10 business days to fully process.
## Read an invoice
An invoice contains your legally registered business name, address, tax ID, invoice number, due date, and more. It also includes a description of any charges and the total amount due. In a typical invoice, the charges are divided into:
- Current plan's subscription amount (if subscribed to a plan)
- Any overage charges (if applicable)
- A plan's credit limit
Let's consider three different examples to understand how your invoice might look. If you subscribe to a [Production](/billing/plans/#production), [Enterprise](/billing/plans/#enterprise), or [On-demand](/billing/plans/#on-demand) plan, one of these scenarios may apply to you.
### Subscription charges
In the first example, the invoice table shows a subscription charge for a Production plan:
| Description | Quantity | Unit price | Amount |
| ------------------------------------------------------------------------------------------ | -------: | ---------: | ---------: |
| _FEB 1 - MAR 1, 2024_ | | | |
| EAS Build - Build (Android: 5 large and 5 medium builds; iOS: 5 large and 5 medium builds) | 1 | $40.00 | $40.00 |
| EAS Build - Plan credit | 1 | -$40.00 | -$40.00 |
| _MAR 1 - APR 1, 2024_ | | | |
| Expo Application Services - Production | 1 | $99.00 | $99.00 |
| **Total (USD)** | | | **$99.00** |
In the above example:
- The first line item describes the EAS Build usage for the billing period of February 1 to March 1, 2024. It contains all the details about how many Android and iOS builds were created during this billing period and their cost.
- The second line item describes the credit limit for the Production plan for the billing period of February 1 to March 1, 2024.
- The third line item describes the subscription charge for the Production plan for the next billing period of March 1 to April 1, 2024.
Since the EAS Build usage doesn't exceed the plan's credit amount, the subscriber only has to pay the subscription amount of the Production plan.
### Overage charges
In the second example, the invoice table shows a subscription charge for a Production plan with an overage charge:
| Description | Quantity | Unit price | Amount |
| --------------------------------------------------------------------------------------------- | -------: | ---------: | ----------: |
| _FEB 1 - MAR 1, 2024_ | | | |
| EAS Build - Build (Android: 15 large and 10 medium builds; iOS 10 large and 15 medium builds) | 1 | $110.00 | $110.00 |
| EAS Build - Plan credit | 1 | -$99.00 | -$99.00 |
| _MAR 1 - APR 1, 2024_ | | | |
| Expo Application Services - Production | 1 | $99.00 | $99.00 |
| **Total (USD)** | | | **$110.00** |
In the above example:
- The first line item describes the EAS Build usage for the billing period of February 1 to March 1, 2024. It contains all the details about how many Android and iOS builds were created during this billing period and their cost.
- The second line item describes the credit limit for the Production plan for the billing period of February 1 to March 1, 2024.
- The third line item describes the subscription charge for the Production plan for the next billing period of March 1 to April 1, 2024.
Since the EAS Build usage exceeds the plan's credit amount for the billing period of February 1 to March 1, 2024, the subscriber has to pay the overage charge and the subscription amount for the Production plan for the next billing period.
### On-demand charges
In the third example, the subscriber is on an On-demand plan. Any charges incurred during the billing period are listed in the invoice:
| Description | Quantity | Unit price | Amount |
| ------------------------------------------------------------------- | -------: | ---------: | ---------: |
| _FEB 1 - MAR 1, 2024_ | | | |
| EAS Build - Build (Android: 10 medium builds; iOS 10 medium builds) | 1 | $30.00 | $30.00 |
| **Total (USD)** | | | **$30.00** |
The above table shows the total amount due based on usage only since the On-demand plan doesn't include any credits.
## Usage-based pricing
Learn how Expo applies usage-based billing for customers who exceed their plan quotas and how to monitor your EAS Build usage.
Expo applies usage-based billing for customers who exceed their [plan](/billing/plans/) allowances. This enables our customers to use what they need without worrying about limitations or requiring contractual obligations.
Usage-based billing is enabled for EAS Build and EAS Update and is billed monthly. We provide an estimate of your existing usage and any overage charges on your [account's Billing](https://expo.dev/settings/billing).
## How usage-based pricing works
### EAS Build
For EAS Build, a flat fee is charged for an individual build executed at higher-priority levels. This is totaled monthly and charged at the end of your billing period or sooner if you cancel your plan.
> **Note**: Builds that are canceled before any work is done are not charged.
[Production, Enterprise, and Legacy plans](/billing/plans/#plans) subscribers receive credits for EAS Build. These credits can be used to offset the cost of builds. They are reset at the start of the billing period and expire at the end of that billing period. Visit our [pricing page](https://expo.dev/pricing) for more information on the pricing schedule for supported build platforms and the available resource classes.
#### Example: EAS Build credit usage
Consider an account subscribed to the Production plan that has 15 medium Android builds, and 10 large iOS builds in a billing period:
| Description | Price | Quantity | Total |
| ----------------------- | ----: | -------: | -----: |
| Android builds (medium) | $1 | 15 | $15 |
| iOS builds (large) | $4 | 10 | $40 |
| EAS Build Credit | | | -$55 |
| **Total (USD)** | | | **$0** |
Since the credit is included in the Production plan, the subscriber pays $0 for their 25 builds.
#### Example: EAS Build credit exceeded
Consider another example where the credit limit is exceeded:
| Description | Price | Quantity | Total |
| ----------------------- | ----: | -------: | ------: |
| Android builds (medium) | $1 | 20 | $20 |
| Android builds (large) | $2 | 10 | $20 |
| iOS builds (medium) | $2 | 15 | $30 |
| iOS builds (large) | $4 | 15 | $60 |
| EAS Build Credit | | | -$99 |
| **Total (USD)** | | | **$31** |
In this scenario, the subscriber pays $31 for 60 builds instead of $130 because the EAS Build Credit covers $99.
### EAS Update
Usage-based pricing for EAS Update comprises two metrics: monthly active users and global edge bandwidth.
The "updated users" reflect the number of unique users who download at least one update in a billing period, also known as "monthly active users" (MAU). Global edge bandwidth represents the total amount of bandwidth used beyond your subscription plan's base bandwidth allocation. If your monthly active users exceed your plan's base MAU allocation, 40 MiB of global edge bandwidth is included for each additional user.
> **Note**: A monthly active user counts only once per billing period, regardless of how many updates this user downloads. In the context of EAS Update, a "user" is considered a unique installation of your app on a device.
Each plan has a number of monthly active users and global edge bandwidth included as part of the subscription. These differ for each plan and the most updated numbers. See our [pricing page](https://expo.dev/pricing), for more information.
#### Example: EAS Update usage
> **Note**: The [On-demand plan](/billing/plans/#on-demand) includes the same amount of monthly active users and global edge bandwidth as the Free plan.
Consider a subscriber to the On-demand plan who deploys 20 updates of 5 MiB each via EAS Update to 10,000 users. The subscription to the plan includes 1,000 monthly active users and 100 GiB per month. As a result, the subscriber's bill for extra usage will be:
| Description | Price | Quantity | Total |
| --------------------- | --------------- | -------- | --------: |
| Updated users | $0.005 per user | 9,000 | $45 |
| Global edge bandwidth | $0.10 per GiB | 525 GiB | $52.5 |
| **Total (USD)** | | | **$97.5** |
Out of the 10,000 users, 1,000 are included in the On-demand plan. As a result, 9,000 are billed for as part of usage-based billing. Paying for 9,000 updated users also includes approximately 351.6 GiB (9000 users \* 40 MiB / 1024).
The global edge bandwidth calculation is:
| Description | Calculation | Quantity |
| ------------------------------------------------- | ----------------------------------- | ------------ |
| Bandwidth used to send updates | 20 updates \* 5 MiB \* 10,000 users | 976.5625 GiB |
| |
| Bandwidth included in plan | | 100 GiB |
| Bandwidth included with 9,000 extra updated users | 9,000 \* 40 MiB | 351.5625 GiB |
| **Total** | **976.5625 - 100 - 351.5625** | **525 GiB** |
If the same subscriber sends the 21st update of 5 MiB to the same 10,000 users in the current billing period, they will only pay for any extra bandwidth used.
| Description | Calculation | Quantity |
| ------------------------------------------------- | ----------------------------------- | -------------- |
| Bandwidth used to send updates | 21 updates \* 5 MiB \* 10,000 users | 1,025.39 GiB |
| |
| Bandwidth included in plan | | 100 GiB |
| Bandwidth included with 9,000 extra updated users | 9,000 \* 40 MiB | 351.5625 GiB |
| **Total** | **1,025.39 - 100 - 351.5625** | **573.83 GiB** |
This is because Expo only charges for [unique monthly active users](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle). As a result, the subscriber's bill for extra usage will be:
| Description | Price | Quantity | Total |
| --------------------- | --------------- | ---------- | ---------: |
| Updated users | $0.005 per user | 9,000 | $45 |
| Global edge bandwidth | $0.10 per GiB | 573.83 GiB | $57.4 |
| **Total (USD)** | | | **$102.4** |
If the same subscriber is on a Production plan, they will pay $0 as the Production plan includes 50,000 monthly active users and 1 TiB (1024 GiB). As such, there is no extra bandwidth usage.
## Monitor usage
> **Note**: Billing estimates shown may be delayed by up to 24 hours (one day).
To see the current billing period's usage summary, go to the [Billing](https://expo.dev/settings/billing) and under **Usage this month**, you will find a summary for both EAS Build and EAS Update usage.
### EAS Build usage history
To see detailed EAS Build usage for a current or previous billing period:
- Click **Usage** in the navigation menu.
- Under the **EAS Build** section, you will find details on builds count and executed builds based on their platform and resource class.
### EAS Update usage history
To see detailed EAS Update usage for a current or previous billing period:
- Click **Usage** in the navigation menu.
- Under the **EAS Update** section, you will find details on updated users and global edge bandwidth details.
### Enable notifications for EAS Build usage
You can enable **Plan credit usage** notifications to closely monitor your EAS Build usage. It enables email notifications when 80% and 100% of your plan's EAS Build credit is used.
To enable EAS Build credit usage notification
- Click **Email notifications** in the navigation menu under your account's settings:
- Under **EAS Build notifications**, click **Subscribe** for **Plan credit usage notifications**.
### How to optimize build usage
You can use [EAS Update](/eas-update/introduction/) and [development builds](/develop/development-builds/introduction/) to test and deploy new code without having to create an entirely new build. This will help you iterate faster and reduce build usage.
For most apps, the JavaScript code changes more frequently than the underlying native code and configuration. If you are building a new build every time for code changes, consider [using EAS Update to take advantage of the different iteration frequency](/eas-update/how-it-works/) between JavaScript and native code. This way, you can ship those changes as an update instead.
When using Continuous Integration (CI)/Continuous Deployment (CD) to build pre-production code, you can reduce unnecessary usage by automating the process of building only when changes are made to the native code. You can create a workflow in your CI/CD using [Expo Fingerprint](https://expo.dev/blog/fingerprint-your-native-runtime) to detect when your native code has changed, and only execute a build if it has changed. Otherwise, publish an update if the native code has not changed.
A development build can run any EAS Update that is compatible with its native runtime. If you are using EAS Update with multiple testing channels, you can reduce the need for creating additional builds by having your testers or test devices use the same development build.
### How to optimize update usage
You can manage certain assets to include or exclude when using EAS Update. This reduces the number of assets uploaded or downloaded from the updates server and the global edge bandwidth used.
To optimize storage and bandwidth usage, you can choose to exclude assets that haven't been modified. For example, images or videos that haven't been changed can be excluded. Excluded assets won't be uploaded to the update server and won't be downloaded by the app. However, it's important to make sure that assets that are not part of an update are included in the native build of the app.
> **Note**: If an app has already downloaded an asset that is also part of a new update, the app will not re-download that asset. This will also not add to your account's bandwidth usage.
You can use `npx expo-updates assets:verify ` to check all required assets are included in the update. For more information, see [Asset selection and exclusion](/eas-update/asset-selection/).
## Plans, Billing, and Payment FAQs
A reference of commonly asked questions on Expo Application Services (EAS) plans, billing, and payment.
This page covers frequently asked questions about plans, billing, and payment for [Expo Application Services (EAS)](/eas/).
## Plans
### How can I update my plan?
To update your Organization account's plan, make sure that you have either an [Owner or Admin role privilege](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. See [Change the role of a member](/accounts/account-types/#change-the-role-of-a-member) for more information.
After confirming your role, see [Upgrade to a new plan](/billing/manage/#upgrade-to-a-new-plan) to upgrade or [Downgrade a plan](/billing/manage/#downgrade-a-plan) to downgrade an existing plan.
### How can I cancel a plan?
See [Cancel a plan](/billing/manage/#cancel-a-plan) for more information.
### What if I subscribe from the wrong account?
If you've subscribed to a plan from the wrong account:
- From the Expo dashboard's navigation menu, under **Account**, switch to the account from which you intend to subscribe.
- Go to [Billing](https://expo.dev/settings/billing) and under **Plan**, [follow the steps to subscribe to the right plan](/billing/manage/#upgrade-to-a-new-plan).
- From the wrong account, go to **[Receipts](https://expo.dev/accounts/[account]/settings/receipts)** and initiate a [request for a refund](/billing/invoices-and-receipts/#request-a-refund).
### I am on a Free plan and only need a few extra builds or updates
If you are on a Free plan and have completed your monthly quota of free builds and updates, upgrade to the [On-demand plan](/billing/plans/). It offers a pay-as-you-go option for EAS Build and the same limits as the Free plan for EAS Update. Once your requirements are fulfilled, you can [downgrade to the Free plan from the On-demand plan](/billing/manage/#cancel-a-plan).
To upgrade from the Free plan to the On-demand plan, see [Upgrade to a new plan](/billing/manage/#upgrade-to-a-new-plan).
To downgrade from an On-demand to a Free plan, see [Cancel a plan](/billing/manage/#cancel-a-plan).
## Billing
### When does a billing period start for a plan?
For the Free plan, the billing period starts on the first day of the calendar month.
For [all paid plans](/billing/plans/#plans), the billing period starts from the subscription date of that plan.
### How do I update my billing information or add a tax ID?
To update your Organization account's billing information or add a tax ID, make sure that you have either an [Owner or Admin role](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. After confirming your role:
- Go to your [account's Billing](https://expo.dev/settings/billing), and click on **Update billing information**. This will take you to Stripe's portal.
- On Stripe's portal, under **Billing information**, click on **Update information** to update billing-related information such as billing name, email, address, and tax ID.
See [Manage billing information](/billing/manage/#manage-billing-information) for more details.
### Can I update the billing information on my last invoice?
No. Updating billing information will only be reflected on the next invoice.
### Can you email me the receipts?
No. Your account's Owner or Admin can download them. See [Download an invoice](/billing/invoices-and-receipts/#download-and-view-an-invoice) for more information.
### How can I reduce the amount of EAS Build usage by using EAS Update?
Use [EAS Update](/eas-update/introduction/) and [development builds](/develop/development-builds/introduction/) to test and deploy new code without creating a new build. This option is better for most apps since JavaScript code changes more frequently than the underlying native code. You can create multiple test channels with EAS Update and reduce the need to create additional builds for your team. Soon, by using [Expo Fingerprint](https://expo.dev/blog/fingerprint-your-native-runtime), you will be able to build or update selectively, depending on whether you already have a compatible native runtime.
For more information, see [How to optimize build usage](/billing/usage-based-pricing/#how-to-optimize-build-usage).
### How do I estimate my next bill?
To estimate your next bill, go to [Billing](https://expo.dev/settings/billing). Under **Usage this month**, you will find a summary of your EAS Build usage based on the [resource class](/build/eas-json/#selecting-resource-class), EAS Update usage based on monthly active users and global edge bandwidth, and the amount spent for both.
See [How usage-based pricing works](/billing/usage-based-pricing/#how-usage-based-pricing-works) for more information.
### What is an EAS Update monthly active user (MAU)?
A monthly active user (MAU) is a unique user of your app that downloads at least one update via EAS Update within a single monthly billing period. See [How are monthly active users counted for a billing period](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle) for more information.
## Payments
### Can I pay annually?
Annual plans are available for [Enterprise plan](/billing/plans/#enterprise) customers. [Contact us](https://expo.dev/contact) for more information.
### How can I update our payment information?
To update your Organization account's payment information, make sure that you have either an [Owner or Admin role](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. After confirming your role:
- Go to your [account's **Billing**](https://expo.dev/settings/billing), and click on **Update billing information**. This will take you to Stripe's portal.
- On Stripe's portal, under **Payment method**, click on **Add payment method** to add a new payment method.
See [Manage billing information](/billing/manage/#payment-method) for more details.
### Can I pay with an Automated Clearing House (ACH), or bank/wire transfer?
[Enterprise plan](/billing/plans/#enterprise) customers on annual plans can pay by ACH as an alternative to credit card payments. [Contact us](https://expo.dev/contact) for more information.
### I need a W-9, or other legal documentation
To request a W-9, [contact us](https://expo.dev/contact).
Find our legal terms at [expo.dev/terms](https://expo.dev/terms).
### Does Expo store my card information?
No, Expo does not. We use Stripe to handle the payment system and they do. See [how Stripe handles security](https://docs.stripe.com/security) for more information.
### How much did I pay for a large build this month?
To view the cost of a large build, go to [Billing](https://expo.dev/settings/billing). Under **Usage this month** you will find a summary of your EAS Build usage based on the [resource class](/build/eas-json/#selecting-resource-class) and the amount spent.
## Add-ons
### How do I increase build concurrencies on my account?
If you're already subscribed to a paid EAS plan, you can buy additional concurrencies in the [**Add-ons** section](https://expo.dev/settings/billing) under **Billing**.
If you're on the Free plan, you'll need to set up a paid subscription. [Click here](https://expo.dev/accounts/[account]/settings/billing/cart) to choose a new plan. Then, select the number of additional concurrencies to add to your subscription on the checkout page.
Each plan has different number of concurrencies included. If you need more than 5 additional concurrencies, [contact us](https://expo.dev/contact).