The template is rebranded and runs on your phone. Now you need a file the stores will accept, and that means Flutter release build signing: a key you create and keep for Android, and your Apple Developer account for iOS. This article covers the first signed build on both platforms, in order.
Before you start
- Set the final package name and bundle ID first. Both stores tie the app to that ID for good, so finish changing your app's package name before you sign anything.
- Set the version in
pubspec.yaml, for exampleversion: 1.0.0+1. The number after+becomes the AndroidversionCodeand the iOS build number. - Use Java 17. That's what our product guides specify. Gradle and Android Gradle Plugin versions differ by product, so check your template's guide rather than upgrading on your own.
Android release build signing
Step 1: Create the upload keystore
Run this once, from any folder:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
On Windows, use a path such as C:\keys\upload-keystore.jks. keytool asks for a password and your name or company. -validity is in days, so 10000 is about 27 years; Android recommends at least 25.
If the terminal says keytool isn't found, it ships with the Java that Android Studio installs. Run flutter doctor -v, find the path after "Java binary at:", and use keytool from that same folder.
Step 2: Fill in android/key.properties
Most Stacklyn templates ship android/key.properties.example. Copy it to android/key.properties (or create that file if your template has no example) and fill it in:
storePassword=YOUR_STORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=upload
storeFile=/absolute/path/to/upload-keystore.jks
Use an absolute path for storeFile. Templates resolve a relative path from different folders (android/app/ in StockPilot and AutoLog, android/ in Agri Ops), and an absolute path works in all of them. On Windows, write it with forward slashes: C:/keys/upload-keystore.jks.
Then protect two files:
- Keep them out of Git. Most of our guides say
key.propertiesand*.jksare already in the template's ignore files. Confirm withgit check-ignore -v android/key.properties; no output means it isn't ignored. - Back up the keystore and both passwords to a password manager or encrypted storage. Every future update needs the same upload key. If you haven't settled who holds it, that's step 7 of the app reskin checklist.
Step 3: Check the release signing config
In most Stacklyn templates, android/app/build.gradle.kts reads key.properties automatically. If the file is missing, the release build falls back to the debug key. It still runs on a device, but Google Play doesn't accept debug-signed apps.
SubZero is the exception: its guide asks you to find the TODO: Add your own signing config comment in build.gradle.kts and add the release config yourself. Flutter's Android deployment guide has the full snippet. The core is:
signingConfigs {
create("release") {
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
storePassword = keystoreProperties.getProperty("storePassword")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
Step 4: Understand Play App Signing
Android uses two keys. The upload key is the one you just made; you sign each .aab with it. The app signing key is held by Google, which re-signs what users download. New apps must use Play App Signing, and new apps must publish as an Android App Bundle.
The practical upside: if you lose the upload key, you can request an upload key reset in Play Console instead of losing the app. Treat that as a fallback, not a plan; the backup in Step 2 still matters.
Step 5: Build the app bundle
flutter clean
flutter pub get
flutter build appbundle --release
The bundle lands at build/app/outputs/bundle/release/app-release.aab. That's the file you upload. Zenith's guide adds AdMob values to this command, so use the version in its guide.
Release builds are always shrunk with R8, per Flutter's docs. Vet Vault, StyleBook, CleanSync and Agri Ops keep their plugin keep rules in android/app/proguard-rules.pro; leave that file in place. CleanSync's guide also says to test a release build on a real device before uploading, and that's good advice for any template: install a release APK (flutter build apk --release) on a real phone and open every screen before uploading.
Step 6: Verify the signature
Check the bundle was signed with your key, not the debug key. Google's client-auth guide uses keytool -printcert -jarfile for this on an APK or AAB:
keytool -printcert -jarfile build/app/outputs/bundle/release/app-release.aab
keytool -list -v -keystore /absolute/path/to/upload-keystore.jks -alias upload
The owner and SHA-256 fingerprint from the first command should match the second. If the owner isn't the name you typed in Step 1, the build used the debug key; go back to Step 2.
iOS signing and upload
You need a Mac with Xcode and an Apple Developer Program membership in your name or your company's.
Register the bundle ID. In Certificates, Identifiers & Profiles, add an Explicit App ID matching the bundle ID in Xcode. Your role must be Account Holder or Admin. Then create the app record in App Store Connect and pick that ID.
Install pods and open the workspace. Run
cd ios && pod install && cd .., then openios/Runner.xcworkspace, not the.xcodeproj.Set signing. Select the Runner target, open Signing & Capabilities, keep Automatically manage signing on and choose your Team. Flutter's docs say automatic signing is enough for most apps. Turn it off only if you need manual signing with specific provisioning profiles, typically on a build server.
Build the archive:
flutter build ipa --releaseYou get an
.xcarchiveinbuild/ios/archive/and an.ipainbuild/ios/ipa/.Upload. Drag the
.ipainto Apple's Transporter app, or open the archive in Xcode (Window > Organizer), click Validate App, then Distribute App. Every upload needs a new build number. Flutter's iOS deployment guide covers the remaining options.
Signing is where first store builds stall: a keystore path resolved from the wrong folder, a password mismatch, a Gradle error that doesn't name the file you got wrong. If you'd rather not, our Flutter app reskinning service creates the upload keystore, wires it into the release build and hands you store-ready signed builds plus the keystore and passwords. Uploading and the store listing stay with you.
Troubleshooting
Gradle says the keystore was tampered with, or the password was incorrect. That wording comes from forum reports, not official docs. Check storePassword and keyPassword against what you typed in Step 1, and remove any quotes or angle brackets around the values. Forum posts also suggest building with the same Java you used to create the keystore.
Gradle can't find a key with your alias. keyAlias must equal the -alias value from Step 1. Run keytool -list -v -keystore <path> and read the "Alias name" line.
Gradle reports the keystore file isn't set, or storeFile is missing, for the release config. This is how developers on forums describe it, and the exact wording differs between reports. Gradle didn't find android/key.properties, or storeFile is empty or wrong. Check the file sits in android/, not android/app/, and use an absolute path.
Play Console rejects the upload as signed in debug mode. This message is also reported on forums. The release build fell back to the debug key, so key.properties wasn't found or, in SubZero, the signing config isn't wired yet. Repeat Steps 2, 3 and 6.
Play says the version code has already been used. Play won't take a versionCode you've already uploaded. Raise the number after + in pubspec.yaml and rebuild.
Xcode shows a signing error, or no team is selected. Choose your team under Signing & Capabilities, and check the bundle ID is registered to that team.
The Stacklyn Labs Help Center has the other setup guides for our templates.