← Catalog

tauri-bundling-mobile-stores

Use when submitting a Tauri v2 mobile build to the iOS App Store or Google Play — `tauri ios build --export-method app-store-connect` with Apple Distribution cert + provisioning profile, uploading via `xcrun altool` (or Transporter / TestFlight); `tauri android build -- --aab` with a `keystore.properties`-driven Gradle signingConfig, AAB vs APK choice, the first-upload-must-be-manual Play Console gotcha, and Play Integrity setup.

Tauri v2 — Mobile store submission (iOS App Store + Google Play)

Pairs with [[tauri-bundling]] (host skill) and [[tauri-bundling-github-actions]] (CI wiring).

Both stores require a signed build, a developer account, and a store listing reserved with a unique bundle/package id. This skill covers the build → sign → upload mechanics; the listing/screenshots/review side is platform-specific UI work in App Store Connect or Google Play Console.

Both Tauri Mobile is current as of Tauri 2.0; iOS dev still requires a macOS host with Xcode installed. Android can build anywhere.


1. iOS — App Store submission

Prerequisites

  • Apple Developer Program membership ($99/yr).
  • macOS host with Xcode + command-line tools (xcode-select --install).
  • Bundle identifier registered in App Store Connect (Identifiers section).
  • An Apple Distribution certificate (not “iOS Distribution”, which is the legacy name) — generated in Certificates, Identifiers & Profiles.
  • A matching App Store provisioning profile for that bundle id.

Local build

The simplest path lets Xcode manage signing — it picks the right cert + profile by matching the bundle id and team:

# One-time: initialize the iOS project
bun run tauri ios init

# Build a release IPA suitable for App Store upload
bun run tauri ios build --export-method app-store-connect

# Output: src-tauri/gen/apple/build/arm64/YourApp.ipa

--export-method values:

ValueWhat it producesWhen
app-store-connectIPA signed with Apple Distribution + App Store profileProduction submission
ad-hocIPA signed with Apple Distribution + Ad-Hoc profile (UDID-restricted)Internal testing without TestFlight
debuggingIPA signed with Apple Development (development cert)Sideload to your own devices

Upload to App Store Connect

# Using App Store Connect API key (preferred for CI):
xcrun altool --upload-app \
  --type ios \
  --file "src-tauri/gen/apple/build/arm64/YourApp.ipa" \
  --apiKey "$APPLE_API_KEY_ID" \
  --apiIssuer "$APPLE_API_ISSUER"

Or upload interactively via Transporter.app (Mac App Store). Once uploaded, the build appears in App Store Connect → TestFlight → Builds within ~5–30 min after Apple’s automated checks pass. From there:

  • TestFlight: invite testers immediately (groups of up to 10,000 external).
  • App Store: attach the build to a version, submit for review (typically 24–48 h review SLA).

CI signing — env vars

For a headless macOS runner you need to import the cert + profile into a temporary keychain (same dance as desktop macOS — see [[tauri-bundling-macos-signing]]). The env vars Tauri reads for iOS:

VarContents
IOS_CERTIFICATEBase64 of the Apple Distribution .p12
IOS_CERTIFICATE_PASSWORDPassword used when exporting the .p12
IOS_MOBILE_PROVISIONBase64 of the .mobileprovision file
APPLE_API_KEY_IDApp Store Connect API key id
APPLE_API_ISSUERApp Store Connect issuer id
APPLE_API_KEY_PATHPath to the .p8 key file

Encode the cert / profile once on your dev box:

base64 -i path/to/cert.p12          | pbcopy   # paste into IOS_CERTIFICATE
base64 -i path/to/profile.mobileprovision | pbcopy   # paste into IOS_MOBILE_PROVISION

ExportOptions.plist

tauri ios build writes one automatically based on --export-method. Override only when you need fine control (signing identity, provisioning style, upload destination). See templates/ExportOptions.plist.


2. Android — Google Play submission

Prerequisites

  • Google Play Console account ($25 one-time).
  • JDK 17 installed (brew install openjdk@17 / apt install openjdk-17-jdk).
  • Android SDK + NDK installed via Android Studio.
  • Application id (e.g. com.example.myapp) — set in tauri.conf.json identifier, propagated to applicationId in build.gradle.kts.

One-time: generate a keystore

# macOS / Linux
keytool -genkey -v \
  -keystore ~/upload-keystore.jks \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000 \
  -alias upload

# Windows (PowerShell)
keytool -genkey -v `
  -keystore $env:USERPROFILE\upload-keystore.jks `
  -storetype JKS `
  -keyalg RSA `
  -keysize 2048 `
  -validity 10000 `
  -alias upload

Back up the keystore in two places. If you lose it, you cannot publish updates to the same Play listing — you’d have to start a new app entry. Print the public-key fingerprint for Play App Signing onboarding:

keytool -list -v -keystore ~/upload-keystore.jks -alias upload

keystore.properties

Create src-tauri/gen/android/keystore.properties:

password=<keystore-password>
keyAlias=upload
storeFile=/absolute/path/to/upload-keystore.jks

Do not commit this file. Add to .gitignore. See templates/keystore.properties for the canonical shape.

Gradle wiring

Edit src-tauri/gen/android/app/build.gradle.kts. Add the import and signingConfigs block above buildTypes:

import java.io.FileInputStream
import java.util.Properties

android {
    // ...

    signingConfigs {
        create("release") {
            val keystorePropertiesFile = rootProject.file("keystore.properties")
            val keystoreProperties = Properties()
            if (keystorePropertiesFile.exists()) {
                keystoreProperties.load(FileInputStream(keystorePropertiesFile))
            }
            keyAlias      = keystoreProperties["keyAlias"]   as String
            keyPassword   = keystoreProperties["password"]   as String
            storeFile     = file(keystoreProperties["storeFile"] as String)
            storePassword = keystoreProperties["password"]   as String
        }
    }

    buildTypes {
        getByName("release") {
            signingConfig = signingConfigs.getByName("release")
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

Build

# App Bundle (recommended for Play Store)
bun run tauri android build -- --aab
# Output: src-tauri/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab

# APK for testing / sideload / outside-store distribution
bun run tauri android build -- --apk

# Per-arch builds (smaller per-user download via Play split delivery)
bun run tauri android build -- --aab --target aarch64 --target armv7
bun run tauri android build -- --apk --split-per-abi

AAB vs APK

AAB (App Bundle)APK
Play StoreRequired for new appsNot accepted for new apps
Direct download / sideloadCannot install directlyYes
Split delivery (per-ABI/density)YesNo (or manually with split-per-abi)
Play App SigningRequiredManual key

Default to AAB. Build APKs only for internal-test rails outside the Store.

Upload — first time

“The first upload must be made manually in the website so it can verify your app signature and bundle identifier.”

Open Google Play Console → your app → Internal testing (or Production) → Create new release → upload the .aab. Subsequent releases can be uploaded via the Play Developer API (e.g. with fastlane supply or the official googleplay action in GitHub Actions).

Play Integrity

Play Integrity (replacement for SafetyNet) is configured in Google Play Console → App integrity. You don’t need any Tauri-side code unless your app calls IntegrityManager from Kotlin/Java to verify the device — most Tauri apps rely only on Play’s automatic protections (license check, malware scan) and don’t need to integrate the SDK directly.

If you do need device-attestation in-app, write a small Kotlin function under src-tauri/gen/android/app/src/main/java/.../IntegrityCheck.kt, expose it through a custom Tauri plugin (see [[tauri-plugin-dev-mobile-bridges]]), and call it from your Rust setup hook.


3. Common errors

SymptomCauseFix
iOS upload: No suitable application records were foundBundle id not registered in App Store Connect, or you reserved it but haven’t created the app entryCreate the app in App Store Connect → My Apps → + before uploading
iOS: No profiles for 'com.example.app' were foundProvisioning profile bundle id mismatchRegenerate profile after fixing bundle id in tauri.conf.json identifier
Android: keystore was tampered with, or password was incorrectWrong password in keystore.propertiesRe-run keytool to confirm; passwords for store and key may differ
Android: Invalid signature from Play ConsoleFirst upload made via API instead of webFirst upload is always manual; switch to API for subsequent uploads
Android: INSTALL_FAILED_NO_MATCHING_ABIS on emulatorBuilt only aarch64 but emulator is x86_64Add --target x86_64 for emulator testing
xcrun altool returns auth error.p8 path wrong, or key not in ~/.appstoreconnect/private_keys/Either move the key there or pass --apiKeyPath explicitly

See also

  • [[tauri-bundling]] — host skill
  • [[tauri-bundling-macos-signing]] — Apple cert / keychain dance reused on iOS
  • [[tauri-bundling-github-actions]] — mobile CI matrix
  • [[tauri-plugin-dev-mobile-bridges]] — when you need native Kotlin/Swift for Play Integrity or App Store-specific APIs
  • templates/keystore.properties — canonical shape with comments
  • templates/build-android.sh — one-shot AAB build with keystore.properties pre-wired
  • templates/ExportOptions.plist — iOS export options for App Store / Ad Hoc / Development