Build a signed app bundle
Build the web app
Run your production web build (for most Ionic Angular, React and Vue projects
npm run build) so the folder set aswebDirin your Capacitor config is fresh.Sync to Android
npx cap sync androidcopies the web build into the native project and updates native plugins. Skip it and you ship yesterday's JavaScript.Open Android Studio
Run
npx cap open android.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.
npm run build
npx cap sync android
npx cap open androidPrefer 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:
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 {
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.
ext {
// ...
targetSdkVersion = 36
}Ionic and Capacitor pitfalls
- Live reload left on. A
server.urlin 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 androidfirst 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
.solibraries 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.
Reproduce on a debug build
Run the app on a phone from Android Studio, open
chrome://inspectin Chrome on your computer and pick the WebView. You get the console, the network panel and breakpoints for your web code.edge://inspectworks the same way in Edge.Inspect a release build only locally
If a bug appears only in release, temporarily set
webContentsDebuggingEnabledtotruein a build you install yourself. Never upload that build: it makes the web content of your app debuggable on any device.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
.aabmakes them readable.
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
.aaband 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.
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:
npm run build
npx cap sync android
npx cap open androidGoogle 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.minWebViewVersiondefaults to60and can't be lower than55. Testers with problems should update Android System WebView from Google Play first. - No mixed content.
android.allowMixedContentis off by default and isn't meant for production. Serve every API and asset over HTTPS, and leaveserver.cleartextat its default offalse. - Sync before every build. Each fix needs
npx cap sync androidafter 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.
- Capacitor: CLI build command
- Capacitor: Setting the target SDK
- Capacitor: Deploying to Google Play
- Android Developers: Sign your app
- Android Developers: Target API level requirements
- Capacitor: Configuration (android options, server options)
- Capacitor: Debugging (chrome://inspect)
- Play Policy: Device and Network Abuse
- Play Console Help: App testing requirements for new personal developer accounts
- Play Console Help: Set up an open, closed, or internal test
- Play Console Help: Deobfuscate or symbolicate crash stack traces
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