How do you sign a React Native Android release with your own upload keystore instead of the template's debug keystore?
answer
- the template signs release with debug
- keytool makes the upload keystore
- MYAPP_UPLOAD_* in gradle.properties
- signingConfigs.release, then buildTypes.release
- upload key vs app signing key
basics
~20 sGenerate an upload keystore with keytool, put its path, alias and passwords in MYAPP_UPLOAD_* Gradle properties, add a signingConfigs.release block that reads them, and point buildTypes.release at it; the fresh template signs release with the debug keystore.
solid answer
~30 sA fresh React Native project's `android/app/build.gradle` signs the `release` build type with `signingConfigs.debug`, and Play refuses debug-signed uploads. The fix follows the docs: generate an **upload keystore** with `keytool -genkeypair` (RSA 2048, validity 10000 days), place it in `android/app`, and define `MYAPP_UPLOAD_STORE_FILE`, `MYAPP_UPLOAD_KEY_ALIAS`, `MYAPP_UPLOAD_STORE_PASSWORD` and `MYAPP_UPLOAD_KEY_PASSWORD` in `~/.gradle/gradle.properties`, which keeps the secrets out of git. Then add `signingConfigs { release { ... } }` reading those properties behind a `project.hasProperty` guard, and set `signingConfig signingConfigs.release` in `buildTypes.release`. With Play App Signing this key only proves uploads come from you; Google re-signs installed APKs with the app signing key it holds.
code
bash · 10 lines# Run once, then store the keystore and its passwords somewhere durable
keytool -genkeypair -v -storetype PKCS12 \
-keystore my-upload-key.keystore -alias my-key-alias \
-keyalg RSA -keysize 2048 -validity 10000
mv my-upload-key.keystore android/app/
# After adding MYAPP_UPLOAD_* to ~/.gradle/gradle.properties
# and signingConfigs.release to android/app/build.gradle:
npx react-native build-android --mode=releasego deeper
Know that the template signs release with the debug keystore and that a Play upload needs your own upload keystore, created with keytool and wired through signingConfigs.release.
Walk through the four MYAPP_UPLOAD_* properties, why they belong outside the repository, and the difference between the upload key you hold and the app signing key Play holds.
Anticipate the side effects: certificate-fingerprint allowlists that break for Play-installed builds, releases built on machines without secrets, and where the keystore and its passwords are backed up.
Own key custody as a process: who may hold the upload key, how it is backed up and rotated, and which systems depend on the app signing certificate's fingerprint.
## Where a fresh project starts A new React Native app's `android/app/build.gradle` defines only one signing config, `debug`, pointing at the `debug.keystore` that ships in `android/app` with the well-known password `android`. Its `release` build type reuses it: ```groovy buildTypes { release { // Caution! In production, you need to generate your own keystore file. signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds } } ``` That makes `--mode release` work out of the box on a laptop, but the result is signed with a public debug key, which Google Play does not accept. Before a first Play release you replace it with your own **upload key**. ## The steps, as the React Native docs describe them 1. **Generate the keystore.** `keytool -genkeypair -v -storetype PKCS12 -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000` asks for passwords and a distinguished name, and writes one key valid for 10,000 days. 2. **Place it** in `android/app`, next to the debug keystore. 3. **Declare four Gradle properties**, preferably in `~/.gradle/gradle.properties` so they never reach the repository: ```properties MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore MYAPP_UPLOAD_KEY_ALIAS=my-key-alias MYAPP_UPLOAD_STORE_PASSWORD=***** MYAPP_UPLOAD_KEY_PASSWORD=***** ``` 4. **Add a release signing config and use it:** ```groovy signingConfigs { release { if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { storeFile file(MYAPP_UPLOAD_STORE_FILE) storePassword MYAPP_UPLOAD_STORE_PASSWORD keyAlias MYAPP_UPLOAD_KEY_ALIAS keyPassword MYAPP_UPLOAD_KEY_PASSWORD } } } buildTypes { release { signingConfig signingConfigs.release } } ``` 5. **Build** with `npx react-native build-android --mode=release` and upload the AAB. The `MYAPP_` prefix is only a naming convention from the docs; Gradle treats them as ordinary project properties. ## Upload key vs app signing key With **Play App Signing**, which any app published as an AAB uses, there are two keys: | Key | Who holds it | What it signs | If lost | |---|---|---|---| | Upload key | You | Each AAB you upload | The account owner can request a reset | | App signing key | Google Play | The APKs installed on devices | Held by Google, not your problem to back up | The upload key proves to Play that an upload came from you. Play then signs the device APKs with the app signing key. That split is what makes a lost upload key recoverable. ## The `hasProperty` guard and other details - **The guard lets the project configure without secrets.** A teammate without the properties can still sync and run debug builds, but a release they build is not signed with the upload key. Check which key signed an artifact before uploading it. - **Plaintext passwords.** The docs suggest macOS Keychain as an alternative to storing the two passwords in `gradle.properties`. - **Never commit the keystore or passwords.** Back the keystore up somewhere durable, together with its alias and passwords, and record who has access. - **Certificate fingerprints change.** Because devices receive APKs signed by the app signing key, any service that allowlists your app by certificate fingerprint needs that key's fingerprint from the Play Console, not only your upload key's. ## Checking what you are about to upload Before the first upload it is worth confirming the artifact is signed by the upload key and not the debug key: - The JDK's `keytool -printcert -jarfile app-release.aab` prints the certificate that signed the bundle; compare its fingerprint with `keytool -list -v` on your upload keystore. - A certificate that matches the template's `debug.keystore` means the release build type still points at `signingConfigs.debug`, or the `MYAPP_UPLOAD_*` properties were missing on that machine. - Test the signed release on a device with `npm run android -- --mode="release"` before uploading, so signing and bundling problems surface locally. ## The scenario: the first Play release of a language-learning app The team has been testing with `--mode release` for weeks, so everything looks ready. The first upload bounces because the AAB is debug-signed. They generate an upload keystore, add the properties on the release machine, wire `signingConfigs.release` and rebuild. The upload is accepted and Play enrols the app in Play App Signing. Later, sign-in through a provider that checks the app's certificate fingerprint fails only in the Play-installed build; the fix is to register the app signing key's fingerprint as well.
- Sign-in that checks the app's certificate fingerprint works in a locally built React Native release but fails when installed from Play; why?With Play App Signing, the APKs Play installs are signed by Google's app signing key, not by your upload key. A service that allowlists the app by certificate fingerprint only knows the upload key's fingerprint, so it rejects the Play build. Register the app signing key's fingerprint, shown in the Play Console, alongside the upload key's.
- Why do the React Native docs recommend ~/.gradle/gradle.properties over android/gradle.properties for the MYAPP_UPLOAD_* values?Both files are read by Gradle, but `android/gradle.properties` lives in the repository and is usually committed, so passwords placed there leak into git history. The home-directory file stays on the machine that builds releases. The docs also mention macOS Keychain as a way to avoid plaintext passwords altogether.
- What does the project.hasProperty('MYAPP_UPLOAD_STORE_FILE') guard in signingConfigs.release protect against?It lets Gradle configure the project on machines without the signing secrets, such as a teammate's laptop or a debug-only CI job, instead of failing on an undefined property. The price is that a release built on such a machine is not signed with the upload key, so it cannot be uploaded; check the signing key before shipping.
saying these in an interview costs you the question
- A fresh React Native project already signs release builds with a production key.
- Committing android/gradle.properties with the keystore passwords is fine for a private repo.
- With Play App Signing, the upload key signs the APKs users install.
- The MYAPP_UPLOAD_* names are special keys the React Native Gradle plugin reads.
- Losing the upload key under Play App Signing means the app can never be updated.