Building & Releasing

Writing and testing your app is most of the work, but getting it into a real user's hands is its own process with its own vocabulary — signing, bundles, store submission, versioning. The exact steps differ depending on whether your project is Expo-managed or a bare React Native CLI project, so this page covers both paths.

Expo-Managed Projects: EAS Build

For Expo-managed projects, EAS Build (Expo Application Services) is the modern, cloud-based way to produce a release build. Instead of needing your own Mac for iOS builds or a fully configured Android Studio setup locally, EAS Build compiles your app on Expo's own build servers and hands you back an installable file.

// Build a release for each platform via Expo's cloud build service
eas build --platform android
eas build --platform ios

EAS Build handles a lot of the fiddly setup automatically — generating and managing signing credentials, running the native compile step, and producing the final installable artifact — which is why it's become the default recommendation for most Expo projects today.

Bare React Native CLI Projects: Building Directly

Without Expo's managed build service, you build using each platform's own native tooling directly. On Android, that means running Gradle's release task from the project's android folder; on iOS, it means opening the project in Xcode and using its Archive workflow.

// Conceptual: build a release APK/AAB directly with Gradle (Android)
cd android
./gradlew assembleRelease

iOS release builds for a bare project are done through Xcode itself (Product → Archive), since Apple's toolchain doesn't offer an equivalent simple one-line command the way Gradle does for Android.

Android App Signing: A Critical Warning

Every Android release build must be cryptographically signed with a keystore — a file containing your app's signing key. The Play Store uses this signature to verify that every update to your app genuinely comes from you, the original publisher.

Critical: if you lose your keystore, you permanently lose the ability to publish updates to your existing app listing under the same identity — the Play Store will refuse any new build that isn't signed with the original key, and there is no recovery process. Back up your keystore file (and its password) somewhere safe and durable — a password manager or an encrypted backup, not just a laptop that could fail.

Android App Bundle (.aab)

The Play Store requires new apps to be submitted as an Android App Bundle (a .aab file) rather than a plain .apk. An app bundle contains all your app's compiled code and resources, and Google Play itself generates and serves optimized, device-specific APKs from it — so a user with a lower-resolution phone doesn't download assets meant for a much larger screen.

iOS Builds: Mac, Xcode, and an Apple Developer Account

Unlike Android, building for iOS genuinely requires a Mac running Xcode — there is no cross-platform way around this requirement, Expo's cloud builds included, since Apple's toolchain only runs on macOS. You'll also need a paid Apple Developer account to sign and submit a build. Submission itself happens through App Store Connect, Apple's web portal for managing your app's listing, builds, and review status.

EAS Submit: Automating Store Submission

For Expo projects, EAS Submit automates the actual upload-to-the-store step as well, taking a build produced by EAS Build and pushing it directly to the Play Store or App Store Connect without you manually uploading files through each store's own console.

eas submit --platform android
eas submit --platform ios

Versioning Your App

Every release needs two distinct version identifiers: a user-facing version string (like 1.2.0) that appears in the store listing, and a platform-specific build number that must strictly increase with every single submission, even ones that only bump a patch version.

// app.json (Expo-managed project)
{
  "expo": {
    "version": "1.2.0",
    "ios": { "buildNumber": "14" },
    "android": { "versionCode": 14 }
  }
}

In a bare React Native CLI project, the same idea is split across package.json's version field (mostly informational) and the native project files themselves — android/app/build.gradle's versionCode/versionName for Android, and the target's version/build settings inside the Xcode project for iOS. Whichever approach you use, forgetting to bump the build number is one of the most common reasons a store submission gets rejected outright.

Over-the-Air (OTA) Updates with expo-updates

One genuinely distinctive React Native/Expo capability is pushing a JavaScript-only bug fix to users' devices without going through app store review again. The expo-updates library checks for a new JS bundle when the app starts, downloads it in the background, and applies it — so a typo fix or a small logic bug can reach every user within minutes instead of the days a full store review can take.

Honest caveat: OTA updates only work for pure JavaScript/asset changes. Anything that touches native code — adding a new native module, upgrading the React Native version itself, changing native permissions — still requires a full new build and a normal store submission. Don't rely on OTA updates as a way to skip store review for changes that actually need it.
Note: the commands and config shown above are correct and meant to be run in your own project's terminal or added to your own config files — this site's own project doesn't run a React Native build or execute any of these commands on this page.
Next up: your app is built and shipped — now make sure it actually runs smoothly for your users. Continue to Performance Tips.