How to Deploy a React Native App to App Store and Google Play
A step-by-step guide to publishing a React Native (Expo) app to the Apple App Store and Google Play Store — from build configuration to store listing and approval.
Getting your React Native app from "works on my phone" to "available in the store" is a rite of passage. The process is more involved than web deployment — you're dealing with signing certificates, provisioning profiles, store review processes, and platform-specific requirements.
This guide covers deploying an Expo-managed React Native app to both stores.
Prerequisites
- Apple: Paid Apple Developer account ($99/year) for App Store
- Google: One-time Google Play Developer fee ($25)
- Expo: Install EAS CLI — the build service that handles native compilation
npm install -g eas-cli
eas loginConfigure app.json
This file is the source of truth for your app's identity:
{
"expo": {
"name": "My Awesome App",
"slug": "my-awesome-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"bundleIdentifier": "com.yourcompany.myapp",
"buildNumber": "1",
"supportsTablet": false,
"infoPlist": {
"NSCameraUsageDescription": "Used to scan QR codes",
"NSPhotoLibraryUsageDescription": "Used to select profile photos"
}
},
"android": {
"package": "com.yourcompany.myapp",
"versionCode": 1,
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"permissions": ["CAMERA", "READ_EXTERNAL_STORAGE"]
}
}
}Critical fields:
bundleIdentifier(iOS) andpackage(Android) must be unique across all apps on each store — use reverse domain notationbuildNumber(iOS) andversionCode(Android) must increment with every store submission- Declare all permissions you use — reviewers check these
Asset Requirements
Before building, prepare your assets:
| Asset | iOS | Android |
|---|---|---|
| App Icon | 1024×1024 PNG (no transparency) | 512×512 PNG |
| Splash Screen | 1284×2778 PNG | 1080×1920 PNG |
| Screenshots | At least 3 per device size | At least 2 |
# Generate all icon sizes automatically from a single 1024x1024 image
npx expo-image-pickerConfigure EAS Build
eas build:configureThis creates eas.json:
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"ios": { "simulator": false },
"android": { "buildType": "apk" }
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}Building for iOS
Set Up Apple Credentials
eas credentials
# Select iOS → choose your profile
# EAS can manage certificates and provisioning profiles automaticallyFor first-time setup, let EAS handle credentials automatically — it creates and manages the signing certificate and provisioning profiles for you. Only manage manually if you have specific enterprise requirements.
Build
# Production build for App Store
eas build --platform ios --profile productionThis uploads your code to Expo's build servers, compiles the native iOS app, and gives you a signed .ipa file. Build takes 10-20 minutes.
Submit to App Store Connect
eas submit --platform ios --latestOr manually upload via Transporter (Mac app from Apple):
- Download Transporter from the Mac App Store
- Drag your
.ipafile in - Click Deliver
Building for Android
Generate Keystore
Android apps must be signed with a keystore. Lose the keystore and you can never update your app under the same package name.
# Let EAS manage it (recommended — they store it securely)
eas credentials
# Select Android → Keystore → Generate new keystoreBack up your keystore. EAS stores it, but download a copy and keep it somewhere safe (password manager, secure cloud storage).
Build
# AAB format required for Play Store
eas build --platform android --profile productionThe .aab (Android App Bundle) format lets Google optimize the download size per device. .apk is for direct installation — only use AAB for Play Store.
Submit to Google Play
eas submit --platform android --latestOr manually in the Google Play Console:
- Go to Production → Releases
- Create new release
- Upload your
.aabfile - Write release notes
- Review and rollout
App Store Listing Requirements
Both stores require this information before approval:
App Store Connect:
- App name (max 30 characters)
- Subtitle (max 30 characters)
- Description (max 4000 characters)
- Keywords (max 100 characters, comma-separated)
- Support URL and Privacy Policy URL
- Age rating
- At least 3 screenshots per device type (iPhone 6.5", iPhone 5.5", iPad Pro 12.9")
Google Play Console:
- Short description (max 80 characters)
- Full description (max 4000 characters)
- Feature graphic (1024×500 PNG/JPG)
- At least 2 screenshots
- Privacy Policy URL
- Content rating questionnaire
Handling Review Rejection
Apple rejects apps more frequently than Google. Common rejection reasons:
Crashes on launch — Test on a real device with the production build before submitting. Simulator behavior differs.
Missing privacy policy — Any app that collects data (including crash reports) needs a Privacy Policy URL. Use a generator for simple apps.
Incomplete permission descriptions — Every NSXxxUsageDescription must explain why your app needs that permission in plain language. "Required for app functionality" is not acceptable.
Missing demo account — If your app requires login, provide a demo account in the App Review Information section. Reviewers won't create their own account.
Using private APIs — Apple scans for undocumented private API usage. This is usually from third-party packages. Check your dependencies.
Over-the-Air Updates with EAS Update
For JavaScript-only changes (no native code changes), you can push updates without going through store review:
# Install expo-updates
npx expo install expo-updates
# Push an update
eas update --branch production --message "Fix cart calculation bug"Users get the update next time they open the app (or on next cold launch, depending on your update strategy). This is invaluable for urgent bug fixes.
Important: EAS Update only works for JavaScript changes. Native code changes, new permissions, or new native packages require a full store build and submission.
Automate Builds with CI/CD
# .github/workflows/deploy.yml
name: EAS Build and Submit
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- uses: expo/expo-github-action@v8
with:
expo-version: latest
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas build --platform all --profile production --non-interactive
- run: eas submit --platform all --latest --non-interactiveKey Takeaways
- Back up your Android keystore — losing it means you can never update your existing app
- Use EAS Build for reproducible, server-side native builds without local Xcode/Android Studio setup
- Increment
buildNumber/versionCodeon every submission — stores reject duplicate build numbers - Provide a demo account for Apple reviewers if your app requires authentication
- Use EAS Update for JavaScript-only hotfixes to avoid the store review wait
- Test your production build on a real physical device before submitting — simulators hide real-device issues

Written by
Sabir KhaloufiFull-stack developer and tech blogger sharing in-depth tutorials on React, Next.js, AI, and modern web development.