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.
npm install -g eas-cli
eas loginThis 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) and package (Android) must be unique across all apps on each store — use reverse domain notationbuildNumber (iOS) and versionCode (Android) must increment with every store submissionBefore 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-pickereas 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": {}
}
}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.
# 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.
eas submit --platform ios --latestOr manually upload via Transporter (Mac app from Apple):
.ipa file inAndroid 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).
# 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.
eas submit --platform android --latestOr manually in the Google Play Console:
.aab fileBoth stores require this information before approval:
App Store Connect:
Google Play Console:
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.
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.
# .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-interactivebuildNumber/versionCode on every submission — stores reject duplicate build numbers