Building and signing app releases
From code to a store file
Remy can already run Garden Defense on a phone plugged into the laptop. But the stores won't take that version. They want a release build: a finished app file, numbered and signed with the studio's secret key.
Garden Defense is a web game inside CapacitorJS, a wrapper that turns web code into an Android project and an iPhone project. This lesson follows our code all the way to the files Google and Apple accept.
Debug vs release builds
- Debug buildFor the team. Quick to make, full of tools for finding bugs, and signed with an automatic practice key. Stores refuse it.
- Release buildFor players. Debugging is switched off, files are shrunk to load fast, and it is signed with our real key.
Android Studio (for Android) and Xcode (for iPhone) can build the same code in two ways.
Always play the release build itself before shipping. Shrinking files and switching off tools can change how an app behaves, so a bug can hide in the release build only.
Quick check
Remy is about to upload Garden Defense to Google Play. Which build should it be?
- A debug build, because it has the most tools
- A release build signed with the studio's key
- Any build, as long as the game runs
Show the answer
A release build signed with the studio's key
Stores only accept release builds signed with our own key. Google Play rejects apps that are still debuggable, because debug tools would let anyone poke around inside.
Two version numbers
Every release has two numbers. The version name, like 1.2.0, is for players and shows on the store page. The version code on Android, called the build number on iPhone, is a plain whole number like 7 that the store uses to tell which build is newest.
The rule: every upload gets a new, higher code, and a code can never be reused, even by a build that never went public. The app's ID, like com.ourstudio.gardendefense, never changes once published, or the store treats it as a brand-new app.
Version bump
1android {2 defaultConfig {3 applicationId "com.ourstudio.gardendefense"4 versionCode 75 versionName "1.2.0"6 }7}
Play Console says: "Version code 7 has already been used." Remy is shipping the 1.2.1 hotfix. What should lines 4 and 5 become?
- versionCode 8 and versionName "1.2.1"
- versionCode 7 and versionName "1.2.1"
- versionCode 8 and a new applicationId
Show the answer
versionCode 8 and versionName "1.2.1"
The version code must go up with every upload, and the version name tells players it's the 1.2.1 hotfix. Changing the applicationId would turn it into a different app.
From web code to an App Bundle
First we build the web game. Then npx cap sync copies those fresh files into the Android and iPhone projects and updates the plugins. npx cap open android opens Android Studio, where Build, Generate Signed App Bundle makes the release file.
That file is an Android App Bundle (.aab), which Google Play requires for new apps. Phones never install it directly. Google Play cuts it into small APKs (the files phones actually install), each made for one kind of screen, chip and language, so downloads stay small.
Bug hunt
1$ npm run build # web files saved to dist/2$ npx cap open android # opens Android Studio3# Build > Generate Signed App Bundle4# Saved: app-release.aab (versionCode 8)
Remy fixed wave5.js and made a new .aab, but testers still see the old bug. Which command did Remy skip?
- npx cap sync, between line 1 and line 2
- git push, after line 4
- npm install, before line 1
Show the answer
npx cap sync, between line 1 and line 2
npm run build makes fresh web files, but the Android project keeps its old copies until npx cap sync copies them in. The order is build, sync, then open and make the bundle.
The signing key
- Lose itWithout the key, an app can never be updated. Players would have to switch to a brand-new app.
- Leak itAnyone with the key and its password could sign a fake update that looks like ours.
- Play App SigningGoogle keeps the real app signing key safe. We only hold an upload key, and if that is lost or leaked, Google can reset it.
- Keep it out of GitList key files in .gitignore, and back up the key and passwords in a password manager.
A signing key is a secret file (a keystore, like garden-release.jks) locked with a password. Signing stamps each build with it, and a phone only accepts an update whose stamp matches the installed app.
Spot the leak
1$ git status2Changes to be committed:3 modified: src/wave5.js4 modified: android/app/build.gradle5 new file: android/app/garden-release.jks6 new file: android/keystore.properties
Kai is about to commit the 1.2.1 hotfix. What should Kai do first?
- Unstage lines 5 and 6, add those files to .gitignore, and keep the key backed up safely
- Commit everything, because the repository is private
- Delete garden-release.jks so nobody can ever steal it
Show the answer
Unstage lines 5 and 6, add those files to .gitignore, and keep the key backed up safely
Line 5 is the signing key and line 6 usually holds its passwords. Private repositories still get shared and copied, and deleting the key would mean losing it, the other disaster.
iPhone builds need a Mac
Apple builds need Xcode, which only runs on a Mac. npx cap open ios opens our iPhone project there.
Apple signing has two parts. A certificate is a digital ID proving a build came from our Apple Developer account. A provisioning profile is a permission slip that ties our app ID and certificate to where the app may run: test phones or the App Store. Xcode's Automatically manage signing creates both. Then Product, Archive makes the release, ready to upload to App Store Connect, Apple's website for our apps.
Bolt says: We have release builds. Before any of them reaches a store, the whole team checks them properly. Next: QA passes and bug tracking.
Fact-checked 9 October 2026
- Android Developers: Version your app developer.android.com
- Android Developers: About Android App Bundles developer.android.com
- Capacitor CLI: cap sync capacitorjs.com