This guide explains how to automate the build and deployment of a React Native Android app to the Google Play Store using GitHub Actions. This setup builds an Android App Bundle (AAB) on every push to main or manually on demand, automatically increments the versionCode and versionName, and uploads the release to the Google Play Console for a chosen track (internal, alpha, beta, or production).
1. Google Play Service Account
- In Google Play Console → Setup → API Access, create a Service Account with permission to upload apps.
- Download the JSON key file for this Service Account.
- Keep this file secure; it will be added to GitHub Secrets.
- [Optional] Click here for video guide on service account creation.
2. Keystore File
- Locate your existing app signing keystore (.jks).
- Convert to Base64 for secure GitHub storage (Windows PowerShell example):
[convert]::ToBase64String((Get-Content -path <your_key_alias> -Encoding byte)) > keystore.txt
- Copy this string — we will store it in GitHub Secrets.
3. GitHub Secrets
In your GitHub repository:
Go to Settings → Secrets and Variables → Actions → New repository secret.
Add the following secrets:
- PLAYSTORE_SERVICE_ACCOUNT_KEY : Paste the full contents of the Google Play JSON key file.
- KEYSTORE_BASE64 : The Base64 string generated from your keystore.
- KEYSTORE_PASSWORD : Your keystore password.
- KEY_ALIAS : Alias used for the key.
- KEY_PASSWORD : Password for the key alias.
4. Android Project Setup
In android/app/build.gradle, configure the signingConfigs:
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
if (keystorePropertiesFile.exists()) {
storeFile file(keystoreProperties["storeFile"])
storePassword keystoreProperties["storePassword"]
keyAlias keystoreProperties["keyAlias"]
keyPassword keystoreProperties["keyPassword"]
}
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
5. Local Testing Setup (optional)
Create a file android/keystore.properties for local builds:
storeFile=your_keystore_filename
storePassword=your_store_password
keyAlias=your_key_alias
keyPassword=your_key_password
Note: This file will be dynamically created in CI from GitHub Secrets.
6. GitHub Actions Workflow
Create a file .github/workflows/deploy.yml with the following:
name: CI/CD to Play Store
on:
workflow_dispatch:
inputs:
track:
description: "Select Google Play deployment track"
required: true
type: choice
options:
- internal
- beta
- alpha
- production
default: beta
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Install Dependencies
run: npm ci
- name: Run Tests
run: npm test
- name: Lint Code
run: npm run lint
- name: Grant Gradle Permissions
run: chmod +x gradlew
working-directory: ./android
- name: Increment Version
run: |
cd android
CURRENT_VERSION_CODE=$(grep -oP 'versionCode \K\d+' app/build.gradle)
NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1))
sed -i "s/versionCode ${CURRENT_VERSION_CODE}/versionCode ${NEW_VERSION_CODE}/" app/build.gradle
echo "Updated versionCode: $CURRENT_VERSION_CODE → $NEW_VERSION_CODE"
CURRENT_VERSION_NAME=$(grep -oP 'versionName "\K[0-9.]+' app/build.gradle)
PATCH=$(echo $CURRENT_VERSION_NAME | awk -F '.' '{print $3}')
NEW_PATCH=$((PATCH + 1))
NEW_VERSION_NAME=$(echo $CURRENT_VERSION_NAME | sed "s/\.[0-9]\+$/\.${NEW_PATCH}/")
sed -i "s/versionName \"${CURRENT_VERSION_NAME}\"/versionName \"${NEW_VERSION_NAME}\"/" app/build.gradle
echo "Updated versionName: $CURRENT_VERSION_NAME → $NEW_VERSION_NAME"
- name: Decode Keystore
run: |
cd android/app
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > my-upload-key.keystore
echo "storeFile=my-upload-key.keystore" > ../keystore.properties
echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> ../keystore.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> ../keystore.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> ../keystore.properties
- name: Build Android Release
run: |
cd android
./gradlew bundleRelease
- name: Upload to Google Play
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
packageName: com.your.package
releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
track: ${{ github.event.inputs.track }}
status: completed
7. Deployment Instructions
Option 1: Automatic deployment on every push to main.
Option 2: Manual deployment with track selection: Go to GitHub → Actions → Select “CI/CD to Play Store” workflow → Run workflow.
A dropdown will appear asking for the Deployment Track — choose internal, alpha, beta, or production.
The workflow will then build and upload the app to the chosen track.
8. Notes
- Keep your keystore file and service account JSON safe.
- The track value determines where your app will be released: internal (Internal Testing), alpha (Closed Testing), beta (Open testing), production (Live on Play Store).
- Make sure your packageName matches exactly with your app’s identifier in the Google Play Console.
This setup works end-to-end when secrets and package details are correct. If you hit a roadblock, share your issue in the comments and I’ll help you out.