Skip to content

Setup guides · Ionic / Capacitor

Google Play closed testing for Ionic and Capacitor apps

Your web app runs inside a native Android project, so the release is built like any Android app. This guide covers the Capacitor-specific parts: the web build, npx cap sync android, signing in Android Studio and the target SDK, then keeping 12 testers opted in for 14 days.

Build
npx cap sync android
Version code
android/app/build.gradle → versionCode
Target API
variables.gradle → targetSdkVersion = 36

Build a signed app bundle

  1. Build the web app

    Run your production web build (for most Ionic Angular, React and Vue projects npm run build) so the folder set as webDir in your Capacitor config is fresh.

  2. Sync to Android

    npx cap sync android copies the web build into the native project and updates native plugins. Skip it and you ship yesterday's JavaScript.

  3. Open Android Studio

    Run npx cap open android.

  4. Generate the bundle

    Choose Build → Generate Signed Bundle/APK, select Android App Bundle, pick or create your keystore, choose the release variant and click Create.

Terminal
npm run build
npx cap sync android
npx cap open android

Prefer the command line or CI? npx cap build android produces a signed bundle with --androidreleasetype AAB and the keystore options. Pass passwords from environment variables, not from a committed file:

Terminal
npx cap build android --androidreleasetype AAB \
  --keystorepath ../upload-keystore.jks --keystorealias upload \
  --keystorepass "$KEYSTORE_PASS" --keystorealiaspass "$KEY_PASS"

Upload key and Play App Signing

Google Play requires Play App Signing for new apps. The keystore you create in the Android Studio wizard (Create new) or pass to npx cap build is your upload key; Google keeps the separate app signing key after your first upload.

Android Studio recommends a validity of at least 25 years for a new key. Store the .jks file outside the android/ folder if you ever regenerate the native project, and back it up together with both passwords. If the upload key is lost, you can request a reset in Play Console.

If you keep build options in your Capacitor config, leave the passwords out of the file you commit. The config is part of your repository.

Version code and target API level

Capacitor doesn't read the version from package.json. The values Google Play sees are in android/app/build.gradle; raise versionCode before every upload:

android/app/build.gradle
android {
    defaultConfig {
        versionCode 2
        versionName "1.0.1"
    }
}

Since 31 August 2026, new apps and updates must target Android 16 (API level 36). Capacitor sets targetSdkVersion in android/variables.gradle, and each major version supports exactly one value: Capacitor 8 uses 36, Capacitor 7 uses 35. Custom values aren't supported, so the way to API 36 is upgrading to Capacitor 8, with your plugins, rather than editing the number.

android/variables.gradle
ext {
    // ...
    targetSdkVersion = 36
}

Ionic and Capacitor pitfalls

  • Live reload left on. A server.url in the Capacitor config makes the app load your development machine's address. Testers see a blank screen. Remove it before the release build.
  • Stale web assets. Generating the bundle in Android Studio without npx cap sync android first ships the previous web build.
  • Plugins on another major version. All @capacitor/* packages and community plugins must match the major version of @capacitor/android. Mismatches cause native build errors or crashes at runtime.
  • Cleartext requests. Android blocks plain http:// traffic by default. Production builds should only call HTTPS APIs.
  • Native plugins and 16 KB pages. Plugins that ship .so libraries must support 16 KB memory pages. Play Console warns about it, and from 1 February 2027 updates without support can't be released.
  • Testing only in the browser. Back button, permissions and deep links behave differently in the Android WebView. Install a release build on a real device before uploading.

Debug what testers see in the WebView

Most problems in a Capacitor release build live in the web layer, so the browser tools you already know are the best way in. In a release build they're switched off: webContentsDebuggingEnabled defaults to false, and Capacitor's own logging follows loggingBehavior, which defaults to debug, so release builds stay quiet.

  1. Reproduce on a debug build

    Run the app on a phone from Android Studio, open chrome://inspect in Chrome on your computer and pick the WebView. You get the console, the network panel and breakpoints for your web code. edge://inspect works the same way in Edge.

  2. Inspect a release build only locally

    If a bug appears only in release, temporarily set webContentsDebuggingEnabled to true in a build you install yourself. Never upload that build: it makes the web content of your app debuggable on any device.

  3. Collect native crashes in Play Console

    Crashes in Capacitor's Java code or in native plugins appear under Monitor and improve → Android vitals → Crashes & ANRs. With R8 on, the mapping file inside the .aab makes them readable.

capacitor.config.ts (local debug build only)
const config: CapacitorConfig = {
  // ...
  android: {
    // Never ship this to Google Play
    webContentsDebuggingEnabled: true,
  },
};

Ask testers for the device model, the Android version and the version of Android System WebView. Many WebView-only bugs depend on that last number, and anything below android.minWebViewVersion is outside what your app supports.

Play Console in short: from closed test to production

Once your Ionic / Capacitor bundle is built and signed, the rest happens in Play Console and works the same for every framework. This is the checklist; the full Play Console walkthrough covers every menu and screen.

  • Finish app setup on the Dashboard: privacy policy, app access, ads, content rating, target audience, data safety and store listing.
  • Create a closed testing release under Test and release → Testing → Closed testing: choose your testers' countries, upload the .aab and send it for review.
  • Add testers with a Google Group on the Testers tab and share the opt-in link. It works once the track shows Published.
  • Keep at least 12 testers opted in for 14 days in a row. Internal testing doesn't count, and someone who leaves and rejoins starts their 14 days again.
  • Ship at least one update during the test and write down what testers found and what you changed.
  • Apply for production from the Dashboard and answer the three sections about your test, your app and your readiness. The decision is Google's; nobody can guarantee approval.

Plan for drop-outs: recruit more than 12 testers so one person leaving doesn't push back your timeline. See the full Play Console steps.

The full Play Console walkthrough

Closed track, Google Group and opt-in link, the 12 testers / 14 days rule and the production application, step by step on one page.

Open the Play Console steps

Ship fixes, and where live updates fit

Rebuild the web app, sync, raise versionCode and generate a new signed bundle for the same closed track:

Terminal
npm run build
npx cap sync android
npx cap open android

Google Play's Device and Network Abuse policy forbids an app from updating itself outside Google Play, with an exception for code that runs in an interpreter or virtual machine, such as JavaScript in a WebView. That's why live update services for the web layer exist. Native code, plugins and permissions always need a new bundle through Google Play.

During the closed test, a new versionCode on the closed track is the straightforward path: it's reviewed, testers get it through Google Play and it shows up in your release history. If you also use a live update service, keep it for genuine fixes and keep the web build in the closed track in sync with it.

  • Mind the WebView minimum. android.minWebViewVersion defaults to 60 and can't be lower than 55. Testers with problems should update Android System WebView from Google Play first.
  • No mixed content. android.allowMixedContent is off by default and isn't meant for production. Serve every API and asset over HTTPS, and leave server.cleartext at its default of false.
  • Sync before every build. Each fix needs npx cap sync android after the web build, or the new bundle still contains the old JavaScript.

Frequently asked questions

Can I use Chrome DevTools on a tester's phone?

Not with the build they installed from Google Play, because release builds don't enable WebView debugging. Ask for the steps to reproduce, install the same version yourself with debugging enabled and inspect it with chrome://inspect.

Why do testers see a blank screen?

Usually because the bundle still contains a server.url from live reload, or because a request fails over plain http://. Check the Capacitor config and the network panel of a debug build first.

Can I stay on Capacitor 7?

Not for new uploads after the target API deadline: each Capacitor major version supports exactly one targetSdkVersion, and Capacitor 7 uses 35, while Google Play needs 36. Plan the upgrade to Capacitor 8 with your plugins before the test starts.

Do I need Android Studio to build the bundle?

No. npx cap build android builds a signed .aab from the command line with your keystore options, which is easier to repeat in CI. Run the web build and npx cap sync android before it in both cases.

Sources

Technical details were checked against the official documentation below. Menus and defaults change between versions; when in doubt, the official page wins.

New to the 12 testers rule?

The general guide explains Google's requirement, how a TestBunny campaign runs day by day and what we can and can't guarantee.

Read the general guide

Need 12 real testers for 14 days?

Add your app and closed testing link, pick Starter (15 testers) or Pro (25 testers), and follow every tester's activity day by day in your panel.

TestBunny isn't affiliated with Google. Testers earn credits, never cash. Nobody can guarantee Google's production access decision, and we don't.