Developer-facing version auto-increment benefit
Setting developer-facing build versions to auto-increment on every build helps avoid manual changes to the project every time you upload a new archive to Play Store testing channels or TestFlight. One common cause for app store rejections is submitting a build with a duplicate version number, which happens when a developer forgets to increment the developer-facing build version before creating a new build.
Configure local version source in eas.json
To set the source of truth for project versions to the local project source code itself, set `cli.appVersionSource` to `local` in your eas.json. With this setup, EAS reads app version values and builds projects as they are without writing to the project. You can enable auto-incrementing versions locally by setting the `autoIncrement` option on a build profile.
User-facing version workflow for production
When doing a production release, the user-facing version should be explicitly set and updated by you. Update the `version` property in app config when the production build is submitted to app stores. This marks the beginning of a new development cycle for a new version of your app.
Configure remote version source in eas.json
To enable remote version source, set `cli.appVersionSource` to `remote` in eas.json. Under the `production` build profile, set the `autoIncrement` property to `true` to automatically increment `android.versionCode` and `ios.buildNumber` values.
App identifier configuration in eas.json
If you have not configured app.json with android.package and/or ios.bundleIdentifier, EAS CLI will prompt you to specify them when you create your first build. The android.package is used as the Android application ID to identify your app on the Google Play Store, and ios.bundleIdentifier is used to identify your app on the Apple App Store.
Difference between internal, preview, and production build profiles
The default EAS configuration includes three build profiles: 'development' profile enables the development client and uses internal distribution for testing builds on development devices, 'preview' profile uses internal distribution for preview testing, and 'production' profile is empty by default and intended for App Store releases. You can also create multiple custom build profiles such as 'debug' and 'testing' in addition to or instead of the defaults.
Build profiles can be customized beyond defaults
While EAS CLI creates default build profiles named 'development', 'preview', and 'production', you can have multiple build profiles with any names you choose, such as 'production', 'debug', 'testing', and so on. Each platform can have different profiles.
Run EAS CLI commands from app directory in monorepo
In a monorepo, run all EAS CLI commands from the root of the app directory. For example, if the project exists at apps/my-app within the git repository, run 'eas build' from the apps/my-app directory, not from the repository root.
EAS Build files location in monorepo
All files related to EAS Build, including eas.json and credentials.json, must be in the root of the app directory. If a monorepo contains multiple apps using EAS Build, each app directory maintains its own copy of these files.
Monorepo postinstall script for dependencies
If a project needs additional setup beyond standard configuration, add a postinstall script to package.json that builds all necessary dependencies in other workspaces. Example: a postinstall script containing 'cd ../.. && yarn build' ensures dependencies in parent workspaces are built before the app.
EAS Build npm cache server behavior
EAS Build runs an npm cache server that speeds up downloading JavaScript dependencies. By default, projects using npm or Yarn 2+ will use the cache. Yarn 1 (Classic) requires a workaround to use the cache. To disable the npm cache server, set the EAS_BUILD_DISABLE_NPM_CACHE environment variable to "1" in eas.json.
Cache key matching search sequence
When restoring cache, the system searches in this order: 1) Exact match to the cache key (automatically generated or explicitly provided), 2) Prefix matches from restore_keys checked sequentially for the most recent match. An exact match is a direct cache hit. A partial match or restore_keys match restores the cache but may not perform as effectively.
ccache configuration environment variables
EAS supports ccache configuration with these environment variables: EAS_USE_CACHE enables both restoring and saving cache when set to 1; EAS_RESTORE_CACHE controls cache restoration at the beginning of a build (1 to enable, 0 to disable, overrides EAS_USE_CACHE); EAS_SAVE_CACHE controls saving the build cache at the end (1 to enable, 0 to disable, overrides EAS_USE_CACHE).
Immutable lockfiles default behavior
By default, Node packages will be installed with the package manager's immutable lockfile flag (for example, yarn --frozen-lockfile or npm ci). To disable this, set the EAS_NO_FROZEN_LOCKFILE environment variable to "1" in eas.json.
Custom cache field in eas.json build profiles
The cache field on build profiles in 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 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.
Disable cache restoration for production builds example
Example eas.json configuration to disable cache restoration for production:
```json
{
"build": {
"production": {
"env": {
"EAS_RESTORE_CACHE": "0",
"EAS_SAVE_CACHE": "1"
}
},
"preview": {
"env": {
"EAS_USE_CACHE": "1"
}
}
}
}
```
Cache restrictions by EAS CLI user
When a build is triggered from eas-cli, caches are scoped to the user running the build. User-scoped caches provide isolation so modifications are not shared during development or between users. When a shared user-actor is used (such as access tokens or GitHub Actions), user-scoped cache rules still apply, meaning builds under that shared account will no longer have isolated caches and run the risk of sharing unintended artifacts.
Production build cache security recommendation
For production builds under a shared user account, disable cache restoration by setting EAS_RESTORE_CACHE to 0. Have designated jobs that only save clean, new caches by disabling cache restoration and only enabling cache saving. Setting EAS_SAVE_CACHE to 1 does not make cache saving exclusive; other jobs with the same environment variable can still save and overwrite the cache.
Cache restrictions by Git branch
When a build is run from GitHub, caches are scoped to the branch the build is running from. A build can restore caches created in the current branch or the default branch (main or master). If no branch-specific cache exists, the build will automatically fallback to restoring caches from GitHub builds on the default branch.
npx testflight non-interactive mode configuration
To run npx testflight in non-interactive mode, provide ascAppId in the submit.production profile in eas.json. This configuration bypasses the process of ensuring your app exists on App Store Connect. Example configuration: {"submit": {"production": {"ios": {"ascAppId": "your-app-store-connect-app-id"}}}}
npx testflight build profile used
The npx testflight command creates a production build using the default EAS production profile to generate an iOS archive (.ipa) file.
npx testflight buildNumber behavior
When running npx testflight subsequently, the buildNumber automatically increments during the bundle identifier confirmation step.
Automatic .npmrc generation when NPM_TOKEN is set
When EAS Build detects the NPM_TOKEN environment variable during a build, it automatically creates a .npmrc file with the following contents: //registry.npmjs.org/:_authToken=${NPM_TOKEN} and registry=https://registry.npmjs.org/. However, this automatic generation only occurs when .npmrc is not already present in the project's root directory.
Configure iOS simulator build profile in eas.json
To build for iOS simulators, modify the build profile in eas.json and set the ios.simulator value to true. Example configuration:
{
"build": {
"preview": {
"ios": {
"simulator": true
}
},
"production": {}
}
}
The profile can be named whatever you prefer, such as preview, local, or simulator.
Build for iOS simulator with eas build command
Execute the command 'eas build -p ios --profile preview' (or with your chosen profile name) to run a build for an iOS simulator when ios.simulator is set to true in eas.json.
Example eas.json with APP_VARIANT environment variable
{
"build": {
"development": {
"developmentClient": true,
"env": {
"APP_VARIANT": "development"
}
},
"production": {}
}
}
Set APP_VARIANT in eas.json build profile env property
In eas.json, use the env property within each build profile to set the APP_VARIANT environment variable. For example, set env.APP_VARIANT to 'development' in the development profile so that when eas build --profile development runs, the app.config.js receives the correct variant identifier.
Example app.config.js with APP_VARIANT switching
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',
}
};
Run development server with APP_VARIANT environment variable
When starting the development server, run APP_VARIANT=development npx expo start to ensure app.config.js is evaluated with the development variant. This can be simplified by adding a script to package.json: {"scripts": {"dev": "APP_VARIANT=development npx expo start"}}
Configure expo-dev-client plugin per app variant
In app.config.js, configure the expo-dev-client plugin with addGeneratedScheme property set based on the variant. Set addGeneratedScheme to !!IS_DEV to disable the app scheme in non-development builds, ensuring QR codes always launch the development build.
Multiple app variants require unique identifiers per variant
To install multiple variants of an app on the same device, each variant must have a unique Application ID (Android) or Bundle Identifier (iOS). This prevents conflicts when multiple versions of the same app are installed simultaneously.
Use APP_VARIANT environment variable to switch bundle identifiers
Define an environment variable named APP_VARIANT in app.config.js to dynamically set ios.bundleIdentifier and android.package. For development variant, set it to 'development' to use suffixed identifiers like com.myapp.dev, and for production, leave it unset to use com.myapp.
Convert app.json to app.config.js for variants
To support multiple app variants on the same device, rename app.json to app.config.js and export the configuration as a JavaScript object. This allows dynamic configuration based on environment variables to switch Application IDs and Bundle Identifiers.
eas.json image field required for GitHub builds
For build profiles used with GitHub, the image field must be specified in eas.json for each native platform. Use 'latest' if the project configuration does not rely on a specific build image. Example configuration shows both android and ios platforms with image set to 'latest'.
Trigger builds from Expo website
Visit the project's build list page at https://expo.dev/accounts/[account]/projects/[projectName]/builds and click the 'Build from GitHub' button. Select a Git ref (branch/commit/tag), the platform to build for, and the build profile. You can also specify a base directory for that specific build.
GitHub PR labels to trigger builds
Add a label to a GitHub PR in the format 'eas-build-[platform]:[profile]' where [platform] is 'android', 'ios', or 'all', and [profile] is a build profile from eas.json. If platform is not specified, defaults to 'all'. If profile is not specified, defaults to 'production'. The build triggers for the latest commit on the PR's base branch. Examples: 'eas-build-android' triggers a production Android build, 'eas-build-ios:staging' triggers a staging iOS build.
iOS enterprise distribution overview
iOS enterprise distribution is for apps intended only for internal use by employees of a large organization and cannot be distributed through the App Store. Unlike ad hoc distribution, the number of devices that can install the app is unlimited and you do not need to manage each device's UDID. Enterprise Distribution requires membership in the Apple Developer Enterprise Program, and organizations must meet additional requirements beyond what is required for App Store distribution.
Apple device limit for ad hoc distribution
Apple's limit for ad hoc distribution is 100 devices per app per year. Disabled devices still count against this limit.
Android APK distribution characteristics
APKs (Android application packages) 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 must be distributed through the Play Store.
Internal distribution build profile configuration
To configure a build profile for internal distribution, set "distribution": "internal" in the eas.json profile. This configuration changes the default behavior for both Android and iOS platforms.
iOS ad hoc distribution overview
iOS ad hoc distribution uses ad hoc provisioning profiles to distribute apps to test devices after they have been registered to an Apple Developer account. This method requires a paid Apple Developer account and that account can only use this method to distribute to at most 100 iPhones per year. The UDID (Unique Device Identifier) of each device must be known, and adding a new device requires a rebuild or re-signing with new credentials.
Android internal distribution default behavior
When "distribution": "internal" is set for an Android build profile, the default gradleCommand will change to generate an APK instead of an AAB. If a custom gradleCommand is specified, it must produce an APK to be directly installable on an Android device. EAS Build will generate a new Android keystore for signing the APK, or use an existing one if the package name matches your development build.
iOS internal distribution provisioning methods
Builds with "distribution": "internal" for iOS will use either ad hoc or enterprise provisioning. With ad hoc provisioning, EAS Build generates a provisioning profile containing an allow-list of device UDIDs, and only devices in the list at build time can install the app. Add devices by running "eas device:create" and creating a new build.
Device registration timing on Apple Developer Program
On new or recently renewed Apple Developer Program memberships, a newly registered device may not be immediately installable. The device is added to your Apple Developer Portal only when it is first included in a provisioning profile. Apple can take up to 24–72 hours to finish processing a newly registered device, and during that time the device cannot be added to provisioning profiles. As a result, the first build or re-sign that includes a new device may fail.
Internal distribution build URL access control
By default, internal distribution build URLs are available to anybody with the URL and each is identified by a 32 character UUID. To require sign-in to an authorized Expo account to access these builds, disable the "Unauthenticated access to internal builds" option in project settings.
List registered devices for ad hoc provisioning
View devices registered via "eas device:create" by running "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 re-sign an existing build with "eas build:resign".
Remove devices from ad hoc distribution
Remove a device from ad hoc distribution by running "eas device:delete". This command will prompt you to disable the device on the Apple Developer Portal. Disabled devices still count against Apple's limit of 100 devices per app for ad hoc distribution.
EAS Workflows build job refresh_ad_hoc_provisioning_profile parameter
For EAS Workflows, set "refresh_ad_hoc_provisioning_profile: true" in the build job's "params" with the same profile requirements as the CLI flag. The profile must set "distribution": "internal" and use credentials managed by EAS.
Running internal distribution builds non-interactively in CI
Internal distribution builds can be run non-interactively in CI using the "--non-interactive" flag with "eas build". For iOS ad hoc builds, this flag reuses a valid provisioning profile without updating its device list. To update the ad hoc provisioning profile, pass "--refresh-ad-hoc-provisioning-profile" with "--non-interactive".
Refresh ad hoc provisioning profile CI requirements
To use "--refresh-ad-hoc-provisioning-profile" in CI, you need at least one device from "eas device:create" and an App Store Connect API key in CI through environment variables (EXPO_ASC_API_KEY_PATH, EXPO_ASC_KEY_ID, and EXPO_ASC_ISSUER_ID) or a key stored in EAS for submissions on the project.
Refresh ad hoc provisioning profile CLI version requirement
The "--refresh-ad-hoc-provisioning-profile" flag requires EAS CLI version 19.1.0 or later.
Refresh ad hoc provisioning profile authentication and device handling
When using "--refresh-ad-hoc-provisioning-profile", EAS authenticates with an App Store Connect API key. It reads devices registered on EAS for your Apple team, registers any missing UDIDs on the portal, and refreshes the profile device list. EAS selects all matching devices for the build target's Apple platform: iPhone and iPad for iOS, Mac for macOS.
Interactive ad hoc provisioning profile update process
After registering a device with "eas device:create", run "eas build" interactively and sign in with your Apple account so EAS can update the ad hoc provisioning profile.
Rename devices for ad hoc distribution
Devices added via the website URL/QR code default to displaying their UDID when selecting them for an EAS Build. Assign friendly names to devices by running "eas device:rename". This renames devices on both Expo and the Apple Developer Portal.
Build profile definition and purpose
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 with custom names.
Extend build profiles using extends option
Build profiles can be extended to other build profile properties using the extends option. For example, preview profile might have extends: production to inherit the configuration of the production profile. You can chain profile extensions up to a depth of 5 as long as you avoid circular dependencies.
Run a build with a specific profile
To run a build with a specific profile, use the command: eas build --profile <profile-name>. If you omit the --profile flag, EAS CLI will default to using the profile named production if it exists.
Development build profile default configuration
By default, eas build:configure creates a development profile with developmentClient: true, indicating the build depends on expo-dev-client. These builds include developer tools and are never submitted to an app store. The development profile also defaults to distribution: internal for easy direct distribution to physical Android and iOS devices.
Platform-specific configuration in build profiles
Inside each build profile, you can specify android and ios fields that contain platform-specific configuration for the build. Options available to both platforms can be provided on the platform-specific configuration object or at the root of a profile.
Development builds for iOS Simulator
To configure development builds to run on the iOS Simulator, use the configuration: ios: { simulator: true }. For iOS, to create a build for internal distribution and another for the iOS Simulator, create a separate development profile with a custom name like development-simulator. No such configuration is required for Android, as the same .apk will run on both device and emulator.