Skip to main content
NinjaNext
Mobile apps

Shipping a React Native App to Google Play, End to End

The release process we use for our own Expo and React Native apps: permanent decisions, a reproducible CI build, real-device testing, the store listing, Data safety and review.

By NinjaNext TechnologiesPublished 6 min read

Key takeaways

  • Decide the package name and signing set-up on day one. Both stay with the app for its whole life.
  • Build release bundles in CI, so every store upload is tested, signed and traceable to a commit.
  • Treat the store listing, privacy policy and Data safety form as part of the product, not paperwork.
  • Leave time in the plan for testing tracks and review before the launch date you announce.

We have published our own Android apps on Google Play since 2019, and our newer games and apps are built with Expo and React Native. This is the release process we use for them, from the first build to a public listing. It is written for founders and product owners who want to know what "ready for the store" really involves, and for developers setting up their first release pipeline.

1. Settle the permanent decisions first

A few choices cannot be changed after your first upload, so we make them before writing much code. The application ID, or package name, is the app's identity on Google Play for good. Ours follow a company namespace, such as com.ninjanext.onemoveleft. One of our oldest apps still carries a namespace from before the company had its current name, which is a good reminder that you live with this choice for the life of the app.

The second is signing. Google Play uses Play App Signing: Google holds the key that signs what users install, and you sign each upload with a separate upload key. A lost upload key can be reset through Play Console support, but a leaked one is a security incident. We create the upload key once, keep it out of the repository, store it in a secrets vault and in the CI provider's encrypted secrets, and record its certificate fingerprint so the pipeline can check it.

The third is permissions. With Expo you can list permissions the app must never request under android.blockedPermissions in app.json. Our offline apps block network and storage permissions explicitly, so a library update cannot quietly add them. Fewer permissions also make the Data safety form shorter and easier to answer honestly.

2. Make the release build reproducible

In our recent apps, release bundles are built by CI, never on a laptop. A push to a dedicated production branch starts a GitHub Actions workflow that runs these steps in order:

  1. Install dependencies from the lockfile, then run the TypeScript check, the linter and the test suite. Any failure stops the release.
  2. Run expo prebuild to generate the native Android project from app.json and config plugins, so nobody edits the native folder by hand.
  3. Apply release signing through a small custom config plugin that reads the upload key from CI secrets.
  4. Build an Android App Bundle with ./gradlew bundleRelease. Google Play requires the AAB format for new apps.
  5. Check that the bundle is signed with the expected upload-key certificate before anything is published.
  6. Attach the signed bundle to a GitHub Release, so every store upload maps to a tagged commit.

Version codes come from the CI run number plus a fixed offset. Google Play rejects an upload whose version code is not higher than the last one, and deriving it in CI means two people can never ship the same number. The version name people see stays in app.json.

Expo's own build service, EAS Build, can do much of this for you. We use plain GitHub Actions because it keeps the whole pipeline in the repository and lets us add our own checks. Either approach works if the result is the same: one merge produces a tested, signed bundle.

3. Test on real devices, not only the emulator

Automated tests catch logic errors. They do not tell you how the app feels on an old phone with a small screen. In our puzzle game One Move Left, the most valuable test is a brute-force solver that checks each of its 100 levels has exactly one winning move. In a prayer times app we maintain, it is unit tests that compare calculated times against published timetables. After that, we install the release bundle on a few physical devices with different screen sizes and Android versions, with large fonts and reduced motion switched on.

Pay special attention to anything that depends on the system: notifications and exact alarms after a reboot, time-zone and daylight-saving changes, the back gesture, dark mode, and right-to-left layouts if you support languages such as Urdu or Arabic. These are the bugs that reviewers and early users find first.

4. Prepare the store listing like a product feature

The listing is the first screen most people see, and Play Console enforces its limits: 30 characters for the app name, 80 for the short description and up to 4,000 for the full description. We keep listing text in the repository and run a script that checks it against those limits, so copy changes are reviewed like code.

Graphics follow the same rule. The 512 x 512 icon, the 1024 x 500 feature graphic and the screenshots are generated by scripts from source files, so a colour change or a new language is a quick re-run rather than an afternoon in a design tool. If your screenshots are marketing renders rather than captures of the real build, label them honestly wherever you reuse them.

5. Get the privacy policy and Data safety form right

Google Play asks for a privacy policy link and a completed Data safety form that declares what the app collects and shares, including data collected by third-party SDKs. Answer it from the code, not from memory: list every SDK in the build and check what each one sends. An analytics or crash-reporting library added for convenience still counts.

Several of our apps declare that they collect no data. That is only true because they have no analytics, ads or accounts and no network access. We publish each app's privacy policy as a small static page and keep its source next to the app, so the policy, the Data safety answers and the code change together.

6. Plan for testing tracks and review

Google Play offers internal, closed and open testing tracks before production. Internal testing is the quickest way to get a signed build onto your team's phones. Newer personal developer accounts must also run a closed test with a minimum number of testers for at least 14 days before they can apply for production access. The rules differ by account type and change over time, so check what applies to yours and build that time into the launch plan.

Review times vary, and a first submission usually takes longest. Common causes of delay are a missing or broken privacy policy link, Data safety answers that do not match what the app does, sensitive permissions without a clear reason, and a target API level below Google Play's current minimum. All four are easier to prevent than to fix against a deadline.

For updates, use a staged rollout where you can. Releasing to a percentage of users first lets you halt a bad build before everyone receives it.

7. Plan for life after launch

Shipping is where maintenance starts. Google Play raises its minimum target API level every year, so even a finished app needs at least one update a year, and libraries need upgrades along the way. A pipeline like the one above is what keeps those updates cheap. One of our apps has been maintained since 2019 and was recently rebuilt on a modern stack, with an automated release pipeline as part of the rebuild, so future updates are a merge rather than an afternoon of manual steps.

If you are planning an Android or cross-platform app and want it handled from first build to store listing, see our mobile app development service. You can also read how One Move Left and MuslimApp were built, or tell us about your app.

Let's build it

Want help putting this into practice?

Tell us what you are building and where you are stuck. We will reply with questions, a suggested approach and next steps.