Skip to content
ShipFastDev
All posts

Article

How to publish an Expo app on Google Play: the checklist we used

By Adil Soukaini

, 8 min read

To publish an Expo app on Google Play you need four things in place: a signed Android App Bundle built with EAS Build, an app record in Play Console that EAS Submit can upload to, a complete store listing (icon, feature graphic, screenshots, privacy policy), and the policy declarations, with account deletion in the app and on the web being the one teams most often miss. This checklist follows the order we used to prepare the Driveway Morocco Android app for Google Play, with the problems we hit along the way.

Driveway Morocco is ShipFastDev's car-rental platform: a Laravel web app plus an Expo SDK 57 companion app that uses the same JSON API (see the Driveway Morocco case study).

1. Get the build pipeline right before you touch Play Console

EAS Build runs the native build in Expo's cloud. Expo's eas.json reference describes three standard profiles: development (a development client with developer tools), preview (production-like builds for the team, installed directly on devices) and production (builds meant for the store). For Android, Expo recommends an .aab for Google Play and an .apk for preview builds.

Turn on automatic version codes. Expo's app version guide points out that "one common cause for app store rejections is submitting a build with a duplicate version number", and recommends letting EAS store the build version remotely with autoIncrement. Our production profile auto-increments the versionCode, so nobody has to remember to bump it.

Our first EAS builds failed three times:

  • Gitignored assets. assets/icon.png and the Android adaptive icon files were listed in .gitignore, so they were never uploaded to EAS and the prebuild step failed with ENOENT on the icon. Anything app.json references must be committed.
  • Native dependency drift. react-native-gesture-handler 3.1.0 was a major version ahead of the ~2.32.0 that SDK 57 expects, which broke the Android prebuild. npx expo install --fix realigned seven mismatched packages. Run it (or npx expo-doctor) before every release build.
  • Slug mismatch. The slug in app.json has to match the EAS project it is linked to, or eas build stops with a mismatch error.

Check the target API level too: Google's target API level page says that from August 31, 2026, new apps and updates must target Android 16 (API level 36) or higher. An Expo project that hasn't been upgraded in a while won't be accepted.

2. Set up EAS Submit and the internal testing track

According to Expo's Google Play submission guide, EAS Submit needs a Play developer account, an app created in Play Console, the android.package name in app.json, and a Google service account key uploaded to EAS. In CI, an EXPO_TOKEN environment variable replaces the interactive login.

Two things we would do the same way again:

  • Give the service account only what it needs. In Play Console we granted it the release permissions (release to production, exclude devices, use Play app signing) and not Admin, which limits what a leaked key could do.
  • Submit to the internal track first. Our eas.json has a submit profile with track: internal. Google's testing tracks page says internal testing takes "up to 100 testers per app", releases reach testers "within minutes", and internal tests "might not be subject to standard Play policy or security reviews". It is the fastest way to check that a real store build installs and talks to the production API.

If you publish from a new personal developer account, plan for the extra step. Accounts created after November 13, 2023 must "run a closed test for their app with a minimum of 12 testers who have been opted in continuously for at least 14 days" before they can apply for production access, according to Google's testing requirements. That is two weeks you can't compress, so line up the testers early.

Once the manual flow worked, we automated it. A GitHub Actions workflow builds the production AAB on EAS whenever master changes (it skips commits that only touch docs or screenshots), then runs eas submit --profile internal --latest. The Expo token and the service account JSON live in GitHub Secrets, never in the repository.

3. Prepare the store listing assets

Google's preview assets guide sets the formats:

  • App icon: 512 x 512 px, 32-bit PNG with alpha, 1024 KB maximum. We resized ours from the app's own icon file.
  • Feature graphic: 1024 x 500 px, JPEG or 24-bit PNG without alpha.
  • Screenshots: at least two across device types and up to eight per device type, between 320 px and 3840 px, with the long side no more than twice the short side. Ours are 1080 x 1920.
  • Short description: 80 characters maximum. Ours is 77.

Use real screens, not mockups. We captured ours on an Android emulator pointed at the production API, and the first set had to be redone: the detail and checkout screens were shot before their data had loaded. The retakes used Maestro flows that assert each screen's content is visible first, the same habit as in our Playwright CI tests.

4. Publish a privacy policy page

Google's User Data policy requires a privacy policy link in Play Console and a link or the text inside the app. It must sit on "an active, publicly accessible and non-geofenced URL (no PDFs)" and cover who you are and how to contact you, what data the app collects and shares and with whom, how you handle it securely, and your retention and deletion policy. The entity named in the store listing, or the app's name, must appear in the policy.

We added a /privacy page to the Laravel site, linked from the footer, so one URL serves the store listing, the app and web visitors. Write it from what the app actually does: for Driveway Morocco, account and booking details, payments processed by Stripe and device information used for notifications.

5. Fill in the Data safety form honestly

Every app on a closed, open or production track must complete the form; apps that live only on the internal track are exempt, per Google's Data safety guide. Even an app that collects nothing must fill it in and link a privacy policy. The guide is explicit that third-party code counts: you declare "user data transmitted off device from your app by libraries and/or SDKs used in your app".

Review your dependencies, not just your code: payments, analytics, crash reporting, push. We declared payment information (handled by Stripe) and email address. The form also asks whether users can request deletion, which brings us to the requirement that catches most teams.

6. Account deletion: in the app and on the web

If users can create an account in your app, Google's account deletion requirements say you must "provide users with an in-app path to delete their app accounts and associated data" and also "a web link resource where users can request app account deletion and associated data deletion". The in-app path should be easy to find, for example in account settings. Deleting an account means deleting the associated data, with limited retention allowed "for security, fraud prevention or regulatory compliance" if you disclose it.

On Driveway Morocco this took three commits on the backend and one in the app:

  • A public /delete-account page on the website, linked from the footer, which is the web resource you enter in Play Console.
  • A token-authenticated DELETE /api/account endpoint that asks for the password again, revokes all of the user's API tokens and deletes the user inside a database transaction.
  • Foreign keys that do the clean-up: reviews, driver verifications, notifications, push tokens and damage reports are deleted with the user; bookings are kept with the user reference set to null, so the financial record stays but is no longer tied to a person. Four tests cover it: authentication required, wrong password, deletion with token revocation, and booking anonymisation.
  • In the app, a "Delete account" action in the profile screen opens a password confirmation dialog. On success the app clears its local session and unregisters the device from push notifications. The strings exist in French, English and Arabic, like the rest of the app.

Decide the retention rules before you write the endpoint: list which tables are deleted and which are anonymised.

The checklist

  1. EAS profiles for development, preview and production; production builds an AAB with auto-incremented version codes.
  2. npx expo install --fix and expo-doctor pass; every asset in app.json is committed.
  3. Target API level meets the current Google Play requirement.
  4. App created in Play Console; service account with release permissions only; key stored in CI secrets.
  5. First build on the internal track, installed and tested against production.
  6. New personal account: closed test with 12 testers for 14 days planned.
  7. Icon 512 x 512, feature graphic 1024 x 500, real screenshots captured after data loads, short description under 80 characters.
  8. Public privacy policy URL, linked in Play Console and inside the app.
  9. Data safety form covering your SDKs as well as your code.
  10. In-app account deletion and a web deletion page, with retention rules written down.

Still choosing the mobile stack? See React Native vs Flutter.

Frequently asked questions

Can EAS Submit upload the very first build of a new app?

Expo's current guide says the default eas submit works for a first submission once the app exists in Play Console and a service account key is set up. A manual path is documented too.

Do I need account deletion if users only log in with Google or Apple?

Google's rule applies whenever users can create an account from within your app. If signing in with Google or Apple creates a user record in your backend, treat it as an account and provide both the in-app path and the web link.

How long does it take to get an Expo app onto Google Play?

Internal testing releases reach testers within minutes. A new personal developer account adds a closed test of at least 12 testers for 14 days before production access, so plan at least two weeks.

Does the internal testing track need a Data safety form?

No. Apps only on the internal track are exempt; the form is required once the app reaches a closed, open or production track.

Sources

  1. Understanding Google Play's app account deletion requirements, Google Play Console Help, accessed 2026-10
  2. Provide information for Google Play's Data safety section, Google Play Console Help, accessed 2026-10
  3. User Data policy, Google Play Console Help, accessed 2026-10
  4. Set up an open, closed, or internal test, Google Play Console Help, accessed 2026-10
  5. App testing requirements for new personal developer accounts, Google Play Console Help, accessed 2026-10
  6. Add preview assets to showcase your app, Google Play Console Help, accessed 2026-10
  7. Meet Google Play's target API level requirement, Android Developers, accessed 2026-10
  8. Submit to the Google Play Store, Expo documentation, accessed 2026-10
  9. Configure EAS Build with eas.json, Expo documentation, accessed 2026-10
  10. App version management, Expo documentation, accessed 2026-10

Want this kind of work on your product?

Tell Adil what you’re building. You get a reply within 1 business day with times for a 15-minute video call.