How to Deploy a React Native App to the App Store and Google Play (2026 Guide)

How to Deploy a React Native App to the App Store and Google Play (2026 Guide)

If you learned how to deploy a React Native app a few years ago, most of what you know about the last mile is now wrong.

The APK you used to upload to Google Play is no longer accepted for new apps. The Application Loader you used to push builds to Apple was removed from Xcode in 2019. The exp build:ios command that Expo documentation once recommended was switched off in January 2023. And the part of the process that now rejects the most releases — privacy declarations, data safety forms, EU trader verification — did not exist at all when most deployment tutorials were written.

The mechanics are not harder than they used to be. In some ways they are dramatically easier: a single command can now build and submit an iOS app from a Linux machine. But the compliance requirements have grown, the deadlines are real, and a release that fails on a missing declaration fails just as completely as one that fails on a broken certificate.

This guide covers the whole path of React Native deployment — accounts, assets, compliance, signing, building, submitting, and what to do when it breaks — for React Native 0.87 and Expo SDK 57, current as of August 2026. It covers two React Native build strategies: EAS Build, which we recommend for most teams, and bare React Native with Xcode and Gradle, for projects that need full local control.

React Native release checklist

If you have shipped before and only want to know what changed, start here.

Accounts

Assets and metadata

  • ☐ App name (30 characters), subtitle/short description, full description
  • ☐ Icons: 1024×1024 without alpha for iOS, 512×512 for Android
  • ☐ Screenshots: iPhone 6.5" (1284×2778), iPad 13" (2064×2752) if you support iPad
  • ☐ Privacy policy URL, live and reachable
  • ☐ Demo account credentials if your app has a login, with the backend actually running

Compliance

  • ☐ Apple App Privacy responses complete
  • PrivacyInfo.xcprivacy present, including required-reason API declarations
  • ☐ EU DSA trader status verified, if you distribute in the EU
  • ☐ Updated age rating questionnaire answered
  • ☐ Google Play Data safety form submitted
  • ☐ Google Play app content declarations complete
  • ☐ In-app account deletion, if your app supports account creation

Technical

  • ☐ Building with Xcode 26 and the iOS 26 SDK
  • ☐ Android targeting API level 36
  • ☐ Android artifact is an AAB, not an APK
  • ☐ Version and build numbers incremented
  • ☐ Release build tested on a real device

1. Accounts and prerequisites

Apple Developer Program

Membership costs $99 per year. Two enrollment types matter:

  • Individual — enroll with your Apple ID. Your personal legal name appears as the seller on the App Store.
  • Organization — your company name appears as the seller, and you get team management with role-based access. Enrollment requires a legal entity and a D-U-N-S number, and Apple verifies your authority to bind the company.

Organization enrollment takes days to weeks depending on how quickly your D-U-N-S record can be verified. If you are planning a launch, start this before you write a line of deployment code. We have seen more launch dates slip on account verification than on any technical problem.

Nonprofits, accredited educational institutions, and government entities can request a fee waiver.

Google Play Console

Registration costs $25, one time. Google requires identity verification — typically a government ID and a credit card in your legal name — and the requirements differ for Personal and Organization account types. Prepaid cards are not accepted.

The 12-tester rule

This one deserves its own heading because it derails first releases more than anything else in this guide.

If your personal Play Console account was created after November 13, 2023, you cannot publish to production until you have run a closed test with at least 12 testers opted in continuously for 14 days. Not 12 sign-ups over a month. Twelve people, opted in, for fourteen consecutive days, at the moment you apply for production access.

Two consequences for planning:

  1. Your closed test has to start at least two weeks before your intended launch date.
  2. You need to actually find twelve people with Google accounts who will opt in and stay opted in.

Organization accounts are not subject to this requirement. If you are planning to publish a React Native app for a client with a launch deadline, checking their account type early is worth the two minutes it takes.

Tooling

  • Node.js 22.13.0 or newer — required by both React Native 0.87 and Expo SDK 57
  • React Native 0.87 — the current stable release (August 11, 2026). If you are on Expo, SDK 57 pairs with React Native 0.86; Expo SDKs track one React Native version each, so do not mix them
  • Xcode 26 or later — mandatory for App Store uploads since April 28, 2026
  • CocoaPods and a compatible Ruby — for bare projects with native dependencies
  • JDK 17 and Kotlin 2.0+ — for Android React Native releases. React Native 0.87 bumps compileSdk and build tools to 37 and adds Android Gradle Plugin 9 support
  • EAS CLI — if you take Path A

2. What to prepare before you start React Native deployment

Half of a deployment goes smoothly or badly depending on whether the metadata was collected up front. Gather all of it before you open either console.

Item

App Store

Google Play

App name

30 characters

30 characters

Subtitle / short description

Subtitle, 30 characters

Short description, 80 characters

Full description

4,000 characters

4,000 characters

Promotional text

170 characters, editable without review

Keywords

100 characters, comma-separated

Not used; the description is indexed

App icon

1024×1024 PNG, no alpha channel

512×512 32-bit PNG with alpha, max 1024 KB

Screenshots

1–10 per size; iPhone 6.5" (1284×2778) required, iPad 13" (2064×2752) if iPad is supported

Minimum 2 across device types; 320–3840px per side. 1080px+ at 16:9 or 9:16 recommended — required for promotional placement. 4 tablet screenshots recommended

Feature graphic

1024×500, JPEG or 24-bit PNG, no alpha

Privacy policy URL

Required

Required

Support URL

Required

Email required

Category

Primary and optional secondary

Primary and optional secondary

Age rating

Questionnaire in App Store Connect

IARC questionnaire in Play Console

Copyright

"2026 Company Name"

Demo account

Required if the app has a login

Required if the app has a login

Two notes on things that trip people up:

The promotional text field is underused. On iOS it can be changed without submitting a new build for review, which makes it the right place for time-sensitive messaging — a sale, an event, a new feature — that would otherwise require a full release cycle.

The demo account is a real requirement, not a formality. App Review guideline 2.1(a) is explicit: include demo account info "and turn on your back-end service!" A staging environment that gets shut down over the weekend is a classic cause of rejection.

Bundle identifier and package name

Both stores identify your app by a reverse-DNS string: com.yourcompany.yourapp. Choose it carefully — it cannot be changed after the first release. Changing it means publishing a new app and losing all reviews, rankings, and installed users.

app
App Information in App Store Connect. The counters under Name and Subtitle show characters remaining out of 30 — the Bundle ID and SKU below them are set once and cannot be changed afterwards.

3. How to deploy a React Native app: compliance

This section did not exist in deployment guides five years ago. It is now where most first-time releases fail.

Apple

App Privacy responses. Before you can submit, you must declare what data your app collects, whether it is linked to the user's identity, and whether it is used for tracking. This includes data collected by third-party SDKs you have integrated — analytics, crash reporting, ad networks. If you cannot say what your ad SDK collects, you cannot complete this form honestly, and inaccurate answers are grounds for removal.

Privacy manifests. Since May 1, 2024, apps must include a PrivacyInfo.xcprivacy file declaring data collection practices and approved reasons for using "required reason APIs" — a set of APIs that could be used for device fingerprinting, such as file timestamps, disk space, and user defaults. Third-party SDKs must ship their own manifests and signatures. In practice this means: upgrade your dependencies before a release, because an outdated SDK without a privacy manifest will fail the upload for you.

EU Digital Services Act trader status. Since February 17, 2025, apps distributed in the European Union must have verified trader status in App Store Connect. Without it, the app is removed from the EU App Store. This is a business-information requirement — trader name, address, phone, email — and it needs to be verified, not just entered.

Age ratings. Apple replaced its age rating system, and responses to the updated questionnaire were due by January 31, 2026. Apps that have not answered may see submissions interrupted.

Account deletion. Guideline 5.1.1(v): if your app supports account creation, it must offer account deletion within the app. Not a support email. Not a web form. In the app.

App Tracking Transparency. If your app tracks users across apps and websites owned by other companies, it must request permission through the ATT framework. The old IDFA questions in App Store Connect are gone; ATT is the mechanism now.

Accessibility Nutrition Labels. App Store Connect now has an App Accessibility section where you declare which of nine accessibility features your app supports — VoiceOver, Voice Control, Larger Text, Dark Interface, Differentiate Without Color Alone, Sufficient Contrast, Reduced Motion, Captions, and Audio Descriptions. Apple states these are voluntary to start and will become mandatory for new apps and updates, with notice beforehand. The bar for declaring support is high: users must be able to complete every common task — first launch, login, core functionality, purchases, settings — using that feature. Filling this in now is cheap; retrofitting accessibility under a deadline is not.

Google

Data safety form. Mandatory for every app published on Google Play, on closed, open, or production tracks. Only internal testing is exempt. You declare what data is collected and shared, why, whether it is encrypted in transit, and whether users can request deletion. As with Apple's form, this covers data collected by third-party SDKs.

App content declarations. Play Console requires a set of declarations before publishing:

  • Privacy policy URL
  • Ads — whether the app contains advertising
  • App access — sign-in credentials for reviewers if any part of the app is gated
  • Target audience and content — declared age group
  • Content rating questionnaire (IARC)
  • Sensitive permissions declaration, if the app requests SMS, Call Log, or similar
  • News app declaration, if applicable
  • Government app declaration, if applicable

Each of these sits in Play Console under Monitor and improve → Policy and programs → App content, and each shows "Needs attention" until completed. A release cannot go to production with outstanding items.

Google Play Console App content page showing five declarations that need attention, including Privacy policy, Content ratings and Data safety, each with a Start or Edit declaration button
App content in Play Console, under Monitor and improve → Policy and programs. Every item here shows "Needs attention" until it is completed, and a release cannot reach production while any of them is outstanding.

Practical advice: treat compliance as a task in your release sprint, not as paperwork you do the morning of the launch. Assign it to a named person. The forms take two to four hours the first time if you have to chase down what your SDKs collect.

4. Platform requirements and deadlines

Both platforms enforce moving minimums. Missing one does not produce a warning — it produces a rejected upload.

Requirement

Deadline

What happens if you miss it

Xcode 26 and iOS 26 SDK

In effect since April 28, 2026

App Store Connect rejects the upload

Apple updated age rating responses

January 31, 2026

Submissions may be interrupted

EU DSA trader status

In effect since February 17, 2025

App removed from the EU App Store

Privacy manifests and required-reason APIs

In effect since May 1, 2024

Upload fails or app is rejected

Android target API 36 for new apps and updates

In effect since August 31, 2026

Play Console blocks the release

Android target API 35 for existing apps

In effect

App unavailable to new users on newer devices

AAB format for new apps

Since August 2021

Play Console rejects APK uploads

 

If the target API bump caught you mid-release, Play Console has an extension path that runs to November 1, 2026. It buys weeks, not months — and it is the last extension on this cycle, so treat it as breathing room for testing rather than a reason to defer the work.

A target API bump is a behavior change, not a version-number change. Android 16 tightens background execution, storage access, and permission behavior. Budget testing time.

5. Choosing your build path

Before you touch a certificate or a keystore, decide how your app gets built. The rest of your release process follows from that choice.

Path A — EAS Build and EAS Submit. Expo's hosted service compiles your app on Expo's infrastructure, manages signing credentials, and uploads to either store with one command. It works for any React Native project, not just ones created with create-expo-app.

Path B — Bare React Native. You build locally with Xcode and Gradle, manage certificates and keystores yourself, and upload through Xcode Organizer or Transporter.

 

Path A: EAS

Path B: Bare

Mac required for iOS builds

No

Yes

Signing credentials

Generated and stored by EAS, or bring your own

You generate and store everything

Time to first build

Minutes

Hours, mostly certificate setup

CI/CD

Built in

You wire it up

Cost

Free tier with a build queue; paid plans for concurrency

Free, but you pay in machine time and maintenance

Native code changes

Config plugins, or commit ios/ and android/

Direct, no indirection

Best for

Most teams, especially without in-house iOS infrastructure

Heavy native customization, on-prem build requirements, existing CI investment

The old "managed versus bare workflow" split no longer describes reality. With Continuous Native Generation, npx expo prebuild generates the ios/ and android/ directories from your app config on demand, and you can commit them or regenerate them at will. You can also run EAS Build on a project that has never used Expo tooling. Treat these as build strategies, not project types.

If you are unsure, start with Path A. Moving from EAS to a local build later is straightforward. The reverse — building certificate infrastructure under deadline pressure — is not.

Flowchart of two React Native deployment paths. React Native source flows into shared prerequisites — accounts, assets, compliance — then splits. Path A, EAS Build, runs EAS credentials, eas build for both platforms and eas submit to both stores. Path B, bare React Native, splits again into an iOS column — App ID and CSR, cert and profile, Xcode archive, Organizer upload — and an Android column — upload keystore, Gradle signing, build the AAB, Play Console — which rejoin at wiring up your own CI. Both paths converge on store consoles for TestFlight and Play tracks, and finally on submitting for review, which is not automatic on either path
The two paths converge at the same two places: the store consoles, and the review submission you still have to make by hand. Everything between the split and that convergence is the part you are choosing between.

6. Path A: Expo / EAS

Install and configure

npm install -g eas-cli
eas login
eas build:configure

eas build:configure creates eas.json in your project root. A production-ready version:

{
  "cli": {
    "version": ">= 20.0.0",
    "requireCommit": true
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "autoIncrement": true,
      "env": {
        "API_URL": "https://api.example.com"
      }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDE12345",
        "ascApiKeyPath": "./private/asc-api-key.p8",
        "ascApiKeyId": "XXXXXXXXXX",
        "ascApiKeyIssuerId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      },
      "android": {
        "serviceAccountKeyPath": "./private/google-service-account.json",
        "track": "internal",
        "releaseStatus": "draft"
      }
    }
  }
}

Three fields deserve attention:

  • android.buildType defaults to app-bundle (AAB) for production, which is what Google Play requires. Set "apk" only for internal preview builds testers install directly.
  • autoIncrement bumps the iOS build number and Android version code automatically. Without it you will eventually waste a build on a duplicate-version rejection.
  • requireCommit forces a clean git state before building. It costs nothing and prevents the "which commit is this build from?" conversation later.

Set cli.version to the version your team actually runs — it is a floor, not a recommendation. EAS CLI moves fast (22.6.0 at the time of writing), and the CLI warns you on every command when a newer one is out.

Credentials

EAS can generate and store everything. On the first production build it offers to create an iOS distribution certificate, a provisioning profile, and an Android keystore. Accept, and they are stored on Expo's servers and reused for every subsequent build.

To inspect, download, or replace them:

eas credentials

If your organization must hold its own keys — a common constraint on enterprise projects — supply them through credentials.json in the project root and point EAS at your local files. Download whatever EAS already generated before switching, and back it up somewhere your team will still be able to reach in two years.

Two facts about iOS credentials worth knowing:

  • The distribution certificate is account-level and you only need one. If it expires, apps already in production keep working; you simply cannot upload new builds until you replace it.
  • Provisioning profiles expire after 12 months and are tied to the distribution certificate. Revoking the certificate invalidates the profiles, so regenerate both together.

For push notifications, use an APNs authentication key (.p8) rather than the older push certificates. One key works across all your apps and it does not expire. Apple allows two per account, so store it carefully.

Terminal window running eas credentials in a project called apiko-blog. The CLI reports that a newer eas-cli version is available, then states that an Expo user account is required to proceed and prompts for email and password
The first thing eas credentials does is check your CLI version and make you log in — credentials live in your Expo account, not on your machine. Run eas login --help if you need a non-interactive login for CI.

Store credentials for submission

iOS — App Store Connect API key. Generate it in App Store Connect under Users and Access → Integrations → App Store Connect API. Download the .p8 file once; Apple will not let you download it again. Record the Key ID and Issuer ID alongside it. This beats an Apple ID with an app-specific password: it does not break when two-factor prompts change, and it works unattended in CI.

Android — Google Service Account. Create a service account in Google Cloud Console, grant it access in Play Console under Users and permissions, and download the JSON key. Grant only what the release process needs — releasing to tracks, not full admin.

Keep both out of version control. Add private/ to .gitignore and distribute the keys through your secrets manager.

Build

# Both platforms
eas build --platform all --profile production

# One at a time
eas build --platform ios --profile production
eas build --platform android --profile production

The build runs on Expo's infrastructure and streams logs to your terminal. To build on your own machine — for compliance reasons, or to debug a failing build — add --local.

Submit

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

--latest picks the most recent finished build for that platform and profile. You can also pass --path ./app.aab to submit a binary built elsewhere.

One caveat that catches every team on their first Android release: the Google Play Developer API cannot create a new app entry. You must create the app in Play Console manually and upload the first release by hand. Every release after that can go through eas submit. Plan for this — discovering it at 6pm on launch day is a bad time.

On iOS, the binary appears in TestFlight roughly 10–15 minutes after upload. It is not submitted to App Review automatically. You still need to open App Store Connect, attach the build to a version, complete the metadata, and submit.

Automate

For CI, generate a personal access token in your Expo account settings, expose it as EXPO_TOKEN, and add --non-interactive:

EXPO_TOKEN=$MY_TOKEN eas build --platform all --profile production --non-interactive --no-wait

EAS Workflows can run this on Expo's infrastructure without a separate CI provider. If you already have GitHub Actions or similar, calling the EAS CLI from your existing pipeline works equally well.

7. Path B: Bare React Native

iOS

Step 1 — Register the App ID. In Certificates, Identifiers & Profiles, go to Identifiers, add a new App ID, and enter your React Native bundle identifier in reverse-DNS form. Enable only the capabilities your app actually uses — every extra capability is an entitlement you will have to justify later.

Step 2 — Create a Certificate Signing Request. Open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority. Enter your email and a common name, choose "Saved to disk," and keep the resulting .certSigningRequest file.

Step 3 — Create the distribution certificate. Under Certificates, add a new certificate and choose Apple Distribution. Upload the CSR, download the .cer, and double-click to install it into your keychain.

Step 4 — Create the provisioning profile. Under Profiles, add a new profile, choose App Store Connect distribution, then select the App ID and the certificate you just created. Download and double-click to install.

Most teams should skip steps 2–4 and let Xcode handle signing: in the target's Signing & Capabilities tab, check "Automatically manage signing" and pick your team. Manual signing earns its complexity in exactly two situations — a CI machine that must not hold an Apple account session, and a project shared across multiple developer teams.

Apple Developer Certificates, Identifiers and Profiles page with the Profiles tab selected, listing one iOS App Store provisioning profile with its expiration date
The Profiles list in Certificates, Identifiers & Profiles. Note the expiration column — profiles last 12 months. The Keys tab in the same sidebar is where APNs authentication keys live.

Step 5 — Prepare the release build. Set the scheme's build configuration to Release (Product → Scheme → Edit Scheme → Run → Build Configuration). Update CFBundleShortVersionString (the user-visible version, e.g. 2.1.0) and CFBundleVersion (the build number, which must increase with every upload).

To skip bundling JavaScript on every debug build, add this to the "Bundle React Native code and images" build phase:

if [ "${CONFIGURATION}" == "Debug" ]; then
  export SKIP_BUNDLING=true
fi

Step 6 — Archive and upload. Open ios/YourApp.xcworkspace — the workspace, not the project. Set the destination to Any iOS Device (arm64), then Product → Archive. When Organizer opens, choose Distribute App → App Store Connect → Upload.

If you prefer not to upload from Xcode, use the Transporter app from the Mac App Store. For scripted uploads, Transporter's command-line tool takes the IPA directly:

iTMSTransporter -m upload -assetFile YourApp.ipa \
  -apiKey XXXXXXXXXX -apiIssuer xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

xcrun altool --upload-app still works for App Store uploads, but altool is deprecated for notarization workflows and Apple is steering new automation toward Transporter and the App Store Connect API. Delivering apps as .itmsp packages is deprecated too — pass the .ipa directly.

Application Loader, which older tutorials still reference, was removed from Xcode in 2019. If a guide tells you to open it, the guide is out of date.

Check before you plan a release: since April 28, 2026, App Store Connect rejects builds not compiled with Xcode 26 and the iOS 26 SDK. If your CI image is pinned to an older Xcode, upgrade it before the release, not after the first rejection.

Android

Step 1 — Generate an upload key.

keytool -genkeypair -v -storetype PKCS12 \
  -keystore my-upload-key.keystore \
  -alias my-key-alias \
  -keyalg RSA -keysize 2048 -validity 10000

Answer the prompts with your organization details. Store the keystore file and both passwords in your password manager immediately — not in Slack, not in the repository.

Step 2 — Wire it into Gradle. Place the keystore in android/app/, then add credentials to ~/.gradle/gradle.properties — the global file, so they never reach version control:

MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore
MYAPP_UPLOAD_KEY_ALIAS=my-key-alias
MYAPP_UPLOAD_STORE_PASSWORD=*****
MYAPP_UPLOAD_KEY_PASSWORD=*****

Reference them in android/app/build.gradle:

android {
    ...
    signingConfigs {
        release {
            if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
                storeFile file(MYAPP_UPLOAD_STORE_FILE)
                storePassword MYAPP_UPLOAD_STORE_PASSWORD
                keyAlias MYAPP_UPLOAD_KEY_ALIAS
                keyPassword MYAPP_UPLOAD_KEY_PASSWORD
            }
        }
    }
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
}

Step 3 — Bump the version. In android/app/build.gradle, increment versionCode (an integer that must increase with every upload) and set versionName to the user-visible version string.

Step 4 — Build the App Bundle.

npx react-native build-android --mode=release

Output lands at android/app/build/outputs/bundle/release/app-release.aab. This is an AAB, and since August 2021 it is the only format Google Play accepts for new apps.

Step 5 — Test the release build before uploading. Uninstall any debug version first, then:

npm run android -- --mode="release"

React Native release builds differ from debug builds in ways that matter: R8 minification can strip code that reflection depends on, and JavaScript is bundled rather than served from Metro. A crash that only appears here is common, and this step is exactly what catches it.

Step 6 — Understand Play App Signing (Play Console → Protected with Play → App signing). When you upload an AAB, Google Play signs the APKs delivered to users with an app signing key that Google holds. Your keystore is the upload key, which only proves the upload came from you.

The practical consequence is reassuring: if you lose your upload key, you are not locked out. Generate a new one, produce a PEM certificate from it, and request a reset through Play Console. Losing an app signing key you chose to manage yourself is the unrecoverable case — a good argument for letting Google hold it.

Apple Developer Certificates, Identifiers and Profiles page with the Profiles tab selected, listing one iOS App Store provisioning profile with its expiration date
App signing in Play Console, under Protected with Play. The app signing key at the top belongs to Google; the upload key certificate below it is yours. "Request upload key reset" at the bottom is the recovery path if you lose it.

Step 7 — Check your target API level. New apps and updates must target API level 36 (Android 16); existing apps must target at least API 35 to stay available to new users on recent devices. Set targetSdkVersion in your Gradle config and test properly.

Note that this is a different field from compileSdk. React Native 0.87 compiles against SDK 37, and that does not satisfy Google's requirement on its own — targetSdkVersion is what Play Console reads. Upgrading React Native and assuming you are covered is a common and expensive mistake.

A target bump is a behavior change, not a number change. Android 16 tightens background execution, storage access and permission behavior, so budget real testing time rather than treating it as a one-line diff.

8. Submitting and review

App Store Connect

  1. Create the app: My Apps → the plus button → New App. Select the bundle ID registered in the developer portal and set an SKU (matching the bundle ID is a reasonable convention).
  2. Fill in the version information: description, keywords, screenshots, support URL, what's new.
  3. Complete App Privacy, age rating, and trader status if you have not already.
  4. Attach the uploaded build to the version. Builds appear here after TestFlight processing finishes.
  5. Set pricing and availability.
  6. Choose the release type: manual, automatic on approval, or phased release over seven days. Phased release is the right default for anything with meaningful traffic — it limits the blast radius of a bad build and can be paused.
  7. Submit for review.

Apple states that "on average, 90% of submissions are reviewed in less than 24 hours". That average holds well for straightforward updates; complex apps and first submissions from new accounts sit in the other 10 percent. Plan for a rejection round on your first submission — it is normal, not a failure.

Play Console

  1. Create the app: All apps → Create app. Set name, default language, app or game, free or paid.
  2. Complete the Dashboard setup tasks and every App content declaration.
  3. Build the store listing: descriptions, icon, feature graphic, screenshots.
  4. Create a release on a track. Start with internal testing, then closed, then production.
  5. Upload the AAB, add release notes, and roll out.

Review times range from a few hours to about seven days. New accounts and apps requesting sensitive permissions take longer. Staged rollout is available on production — start at 10–20% and watch your crash rate before going wide.

Common rejection reasons

Reason

What actually happened

Fix

Guideline 2.1 — App Completeness

Demo account did not work, backend was off, or a URL was a placeholder

Test the demo account on a clean device the day you submit

Guideline 4.3 — Spam

The app is too similar to something already published, or you submitted per-client variants of one app

Consolidate into a single configurable app

Guideline 5.1.1 — Data collection

Requesting data not needed for core functionality, or no in-app account deletion

Remove unnecessary permission requests; add account deletion

Privacy manifest missing

A third-party SDK shipped without one

Update dependencies

Data safety mismatch

Declared behavior does not match observed behavior

Re-audit what your SDKs actually send

Broken metadata

Screenshots showing a different app version, placeholder text

Review the listing as a user would

When you are rejected, Apple's Resolution Center is a conversation, not a verdict. Reply with specifics. If you believe the reviewer misread the app, you can request an appeal — but a clear reply with a screen recording resolves most cases faster.

9. Automating releases

Manual releases are fine for the first one and a poor use of anyone's time after that.

  • EAS Workflows — build, submit, and run checks on Expo's infrastructure, configured in YAML alongside your project. The lowest-effort option if you are already on Path A.
  • Fastlane — the long-standing tool for both platforms. Strong fit if you are on Path B and want scripted certificates, screenshots, and metadata.
  • Xcode Cloud — Apple's own CI, well integrated with App Store Connect and TestFlight, iOS only.
  • GitHub Actions and similar — call either EAS CLI or Fastlane from a pipeline you already maintain.

Whatever you choose, aim for the same target: a tagged commit produces a build, the build lands in a testing track, and promotion to production is a deliberate decision by a human.

10. Troubleshooting

Symptom

Likely cause

Fix

ITMS-90189 / duplicate build number

CFBundleVersion was not incremented

Bump the build number; enable autoIncrement on EAS

"Missing Compliance" blocks the build in TestFlight

Export compliance question unanswered

Answer in App Store Connect, or set ITSAppUsesNonExemptEncryption in Info.plist

Upload rejected: SDK version

Built with an Xcode older than 26

Upgrade Xcode and your CI image

"You uploaded an APK. Upload an Android App Bundle"

Built a React Native APK

npx react-native build-android --mode=release

Play Console: version code already used

versionCode not incremented

Increment; it must strictly increase

Provisioning profile does not include signing certificate

Certificate was regenerated after the profile

Regenerate the profile against the current certificate

Release build crashes on launch, debug build works

R8 stripped code used via reflection, or a missing bundle

Check ProGuard rules; test with --mode="release" locally

App rejected for a privacy manifest in a dependency

Outdated third-party SDK

Update dependencies, then re-upload

eas submit fails on the first Android release

App does not exist in Play Console yet

Create it and upload the first AAB manually

FAQ

How much does it cost to publish a React Native app? $99 per year for the Apple Developer Program and $25 once for Google Play Console. EAS Build has a free tier; paid plans buy concurrency and faster machines.

How long does review take? Apple reviews 90% of submissions in less than 24 hours on average. Google ranges from a few hours to about a week, longer for new accounts. Neither is guaranteed — do not schedule a launch event against a review queue.

Can I publish an iOS app without a Mac? Yes, with EAS Build, which compiles on macOS infrastructure and submits for you. You will still need App Store Connect in a browser for metadata and submission.

What is the difference between an AAB and an APK? An APK is a complete installable package. An AAB is a publishing format from which Google Play generates optimized APKs per device configuration, producing smaller downloads. Google Play has required AAB for new apps since August 2021; APKs remain fine for direct distribution outside the store.

What happens if I lose my keystore? With Play App Signing, you generate a new upload key and request a reset through Play Console. Without it — if you manage your own app signing key and lose it — you cannot update the app and must publish a new listing. This is the single strongest argument for Play App Signing.

Do I have to use Expo? No. EAS Build works on any React Native project, and bare projects build fine with Xcode and Gradle. The choice is about where builds run and who manages credentials, not about which framework you use.

Why was my app rejected when the previous version was approved? Guidelines change, reviewers differ, and requirements introduced since your last release apply to the new build. The Resolution Center message names the specific guideline — start there.

Deploying is the beginning, not the end

The first release is the hardest one. After it, the process compresses into a command and a checklist — as long as someone keeps track of the moving deadlines: target API bumps every year, Xcode minimums every spring, and new compliance declarations whenever regulators add one.

If you would rather have that handled by a team that ships React Native apps regularly, we do this for a living.  To learn more about navigating framework constraints and seeing real-world production setups, check out our guide on React Native limitations or explore our portfolio of open-source React Native apps.