flutter-signing
Scannednpx machina-cli add skill nylo-core/claude-code/flutter-signing --openclawFlutter Code Signing
Overview
Code signing is required to distribute apps on both iOS and Android. iOS uses certificates and provisioning profiles managed through Apple Developer Program. Android uses Java keystores containing a private key. Both must be properly configured before building release versions.
When to Use
- User needs to build a release version for the first time
- User sees signing-related build errors
- User is setting up CI/CD and needs to configure signing
- User is preparing to submit to App Store or Google Play
- User needs to create or manage signing certificates/keystores
- NOT for debug builds (Flutter handles debug signing automatically)
Quick Reference
| Platform | Signing Mechanism | Key Files | Where Configured |
|---|---|---|---|
| iOS (auto) | Xcode Automatic Signing | Apple Developer account | Xcode project settings |
| iOS (manual) | Certificate + Provisioning Profile | .cer, .p12, .mobileprovision | Xcode project settings |
| Android | Java Keystore | .jks or .keystore file | key.properties + build.gradle |
Part 1: iOS Code Signing
Prerequisites
- Apple Developer Program membership ($99/year)
- Xcode installed on macOS
- Apple ID added to Xcode (Xcode → Settings → Accounts)
Automatic Signing (Recommended for Development)
Automatic signing lets Xcode manage certificates and provisioning profiles.
Setup in Xcode:
- Open
ios/Runner.xcworkspacein Xcode - Select the Runner project in the navigator
- Select the Runner target
- Go to Signing & Capabilities tab
- Check Automatically manage signing
- Select your Team from the dropdown
Xcode will automatically:
- Create a signing certificate if needed
- Create and update provisioning profiles
- Handle capability entitlements
In project.pbxproj, automatic signing looks like:
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = YOUR_TEAM_ID;
Manual Signing (Required for CI/CD and Distribution)
For App Store distribution or CI/CD, you need manual signing with explicit certificates and profiles.
Step 1: Create a Signing Certificate
Via Apple Developer Portal:
- Go to developer.apple.com/account
- Navigate to Certificates, Identifiers & Profiles → Certificates
- Click + to create a new certificate
- Choose the certificate type:
- Apple Development — for development/testing
- Apple Distribution — for App Store and Ad Hoc distribution
- Upload a Certificate Signing Request (CSR):
- Open Keychain Access on macOS
- Menu: Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority
- Enter your email, select Saved to disk, click Continue
- Upload the generated
.certSigningRequestfile
- Download the
.cerfile and double-click to install in Keychain
Export as .p12 (for CI/CD):
- Open Keychain Access
- Find your certificate under My Certificates
- Right-click → Export
- Save as
.p12format - Set a strong password (you will need this in CI/CD)
Step 2: Register an App ID
- Go to Certificates, Identifiers & Profiles → Identifiers
- Click + → App IDs → App
- Enter a description and your Bundle ID (e.g.,
com.company.appName) - Select required Capabilities (Push Notifications, Sign in with Apple, etc.)
- Click Register
Step 3: Create a Provisioning Profile
- Go to Certificates, Identifiers & Profiles → Profiles
- Click + to create a new profile
- Choose the profile type:
- iOS App Development — for development
- App Store Connect — for App Store distribution
- Ad Hoc — for limited distribution outside the store
- Select the App ID you registered
- Select the Certificate to include
- For Development/Ad Hoc: Select the Devices to include
- Name the profile and click Generate
- Download the
.mobileprovisionfile
Step 4: Configure Xcode for Manual Signing
In Xcode:
- Uncheck Automatically manage signing
- Under Signing (Release), select the provisioning profile
- The certificate should auto-populate
In project.pbxproj, manual signing looks like:
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = YOUR_TEAM_ID;
PROVISIONING_PROFILE_SPECIFIER = "Your Profile Name";
CODE_SIGN_IDENTITY = "Apple Distribution";
iOS Signing for CLI Builds
When building from the command line:
# Build with automatic signing
flutter build ipa
# Build with specific export options
flutter build ipa --export-options-plist=ios/ExportOptions.plist
ExportOptions.plist for App Store distribution:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>com.company.appName</key>
<string>Your Distribution Profile Name</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
</dict>
</plist>
ExportOptions.plist for Ad Hoc distribution:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>ad-hoc</string>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>signingStyle</key>
<string>manual</string>
<key>provisioningProfiles</key>
<dict>
<key>com.company.appName</key>
<string>Your Ad Hoc Profile Name</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
</dict>
</plist>
iOS CI/CD Signing
For CI/CD environments (GitHub Actions, Codemagic, Bitrise, etc.) that don't have Xcode GUI:
Using Fastlane Match (Recommended)
Fastlane Match stores certificates and profiles in a Git repo or cloud storage:
# Install fastlane
gem install fastlane
# Initialize match (in ios/ directory)
cd ios
fastlane match init
Matchfile configuration:
git_url("https://github.com/your-org/certificates.git")
storage_mode("git")
type("appstore") # or "development", "adhoc"
app_identifier("com.company.appName")
team_id("YOUR_TEAM_ID")
# Generate/download certificates and profiles
fastlane match appstore
fastlane match development
Manual CI/CD Setup (GitHub Actions Example)
- name: Install Apple certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# Decode from base64
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: \
-k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Install provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
To encode your files for CI secrets:
# Encode certificate
base64 -i certificate.p12 | pbcopy
# Encode provisioning profile
base64 -i profile.mobileprovision | pbcopy
Part 2: Android Code Signing
Step 1: Create a Keystore
keytool -genkey -v \
-keystore ~/upload-keystore.jks \
-storetype JKS \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias upload
You will be prompted for:
- Keystore password: Strong password for the keystore file
- Key password: Password for the individual key (can match keystore password)
- Distinguished name fields: Name, organization, city, state, country code
Important: Store the keystore file and passwords securely. If you lose them, you cannot update your app on Google Play (unless using Play App Signing).
Step 2: Create key.properties
Create android/key.properties (this file should NOT be committed to version control):
storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=upload
storeFile=/Users/username/upload-keystore.jks
For team projects, use a relative path or environment variable:
storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=upload
storeFile=../keystore/upload-keystore.jks
Step 3: Configure build.gradle
Edit android/app/build.gradle:
// Add above the android { } block
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
// ... existing config ...
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
// Enables code shrinking, obfuscation, and optimization
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
Step 4: Add to .gitignore
Ensure sensitive files are not committed:
# Android signing
android/key.properties
*.jks
*.keystore
Google Play App Signing
Google Play App Signing adds an extra layer of security. Google holds the app signing key and you upload with an "upload key":
- App signing key: Held by Google, used to sign the APK/AAB delivered to users
- Upload key: Your keystore, used to authenticate uploads to Google Play
If you lose your upload key, you can contact Google to reset it. You cannot lose the app signing key because Google manages it.
Enrollment: When uploading your first AAB to Google Play Console, you're automatically enrolled in Play App Signing.
Android CI/CD Signing
Environment Variable Approach
// android/app/build.gradle
signingConfigs {
release {
keyAlias System.getenv("KEY_ALIAS") ?: keystoreProperties['keyAlias']
keyPassword System.getenv("KEY_PASSWORD") ?: keystoreProperties['keyPassword']
storeFile file(System.getenv("KEYSTORE_PATH") ?: keystoreProperties['storeFile'] ?: 'dummy')
storePassword System.getenv("STORE_PASSWORD") ?: keystoreProperties['storePassword']
}
}
GitHub Actions Example
- name: Decode keystore
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
run: |
echo "$KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks
- name: Build AAB
env:
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
run: |
# Create key.properties for the build
cat > android/key.properties <<EOF
storePassword=$STORE_PASSWORD
keyPassword=$KEY_PASSWORD
keyAlias=$KEY_ALIAS
storeFile=upload-keystore.jks
EOF
flutter build appbundle
To encode keystore for CI secrets:
base64 -i upload-keystore.jks | pbcopy
Codemagic / Bitrise
Most CI/CD platforms have built-in support for Android code signing:
- Upload the keystore file to the platform's secure files
- Set environment variables for passwords and alias
- The platform injects them at build time
Signing Configuration Summary
iOS File Locations
| File | Purpose |
|---|---|
ios/Runner.xcodeproj/project.pbxproj | Signing style, team ID, profile specifier |
ios/Runner/Info.plist | Bundle identifier (used to match profiles) |
ios/ExportOptions.plist | Export configuration for CLI builds |
~/Library/MobileDevice/Provisioning Profiles/ | Installed provisioning profiles |
| Keychain | Installed certificates |
Android File Locations
| File | Purpose |
|---|---|
android/key.properties | Keystore credentials (NOT in version control) |
android/app/build.gradle | Signing config referencing key.properties |
upload-keystore.jks | The keystore file (store securely) |
Common Mistakes
-
Committing key.properties or keystore to Git — These contain sensitive credentials. Add
android/key.properties,*.jks, and*.keystoreto.gitignoreimmediately. If accidentally committed, rotate the credentials. -
Expired provisioning profile — iOS provisioning profiles expire after 1 year. If builds fail with "no valid provisioning profile," regenerate the profile in the Apple Developer Portal and download it.
-
Certificate/profile mismatch — The provisioning profile must include the certificate you're signing with. If you create a new certificate, you must regenerate your provisioning profiles to include it.
-
Wrong bundle ID in provisioning profile — The bundle ID in the provisioning profile must exactly match
PRODUCT_BUNDLE_IDENTIFIERin the Xcode project. Wildcards (e.g.,com.company.*) work for development but not App Store distribution. -
Lost Android keystore — If you lose the keystore file or forget the passwords and are NOT enrolled in Google Play App Signing, you cannot update your app. You would need to publish as a new app with a new package name. Always back up your keystore securely.
-
Using debug signing for release — If
signingConfigis not set for the release build type, Gradle may silently fall back to debug signing. The resulting APK/AAB will be rejected by Google Play. -
Forgetting to configure build.gradle — Creating
key.propertiesis not enough. Thebuild.gradlefile must load and reference it in thesigningConfigsblock. Without this, the keystore is ignored. -
Xcode automatic signing in CI — Automatic signing requires Xcode to be logged into an Apple Developer account, which is not available in headless CI environments. Use manual signing with explicit certificates and profiles for CI/CD.
-
Multiple keystores for different flavors — If your app has build flavors (dev, staging, production), each may need its own signing config. Configure separate
signingConfigsblocks for each flavor inbuild.gradle. -
Not enabling Play App Signing — Google Play App Signing protects against keystore loss and enables key upgrades. All new apps are automatically enrolled. For existing apps, enrollment is strongly recommended but requires careful migration.
Source
git clone https://github.com/nylo-core/claude-code/blob/main/skills/flutter-signing/SKILL.mdView on GitHub Overview
Code signing is essential to distribute Flutter apps on iOS and Android. It covers iOS certificates and provisioning profiles, Android keystores, signing configurations, and CI/CD signing setup to ensure release builds are trusted by app stores.
How This Skill Works
Use Xcode Automatic Signing for iOS development, or manual signing with certificates, App IDs, and provisioning profiles for distribution. Android signing relies on a Java keystore configured via key.properties and signingConfigs in build.gradle. For CI/CD, export and manage signing assets (like .p12 and keystores) securely and reference them in your pipeline.
When to Use It
- Building a release version for the first time
- Fixing signing-related build errors
- Setting up CI/CD signing
- Preparing to submit to App Store or Google Play
- Creating or managing signing certificates/keystores
Quick Start
- Step 1: In Xcode, choose Automatic Signing for iOS (or prepare manual signing with certificates and provisioning profiles)
- Step 2: If using CI/CD, export the signing certificate as .p12 and securely store the Android keystore/credentials
- Step 3: For Android, add the keystore to the project, configure key.properties and signingConfigs in build.gradle, then run flutter build release
Best Practices
- Prefer Automatic Signing for development to let Xcode manage certificates and profiles
- Use manual signing for distribution or CI/CD and keep certificates, keys, and provisioning profiles secure
- Export and store signing assets (e.g., .p12, .jks/.keystore) securely in vaults or CI/CD secrets
- Keep App IDs/Bundle IDs, provisioning profiles, and keystores aligned with your Flutter project
- Regularly rotate signing certificates and update provisioning profiles in your repo
Example Use Cases
- Enable Xcode Automatic Signing for a new Flutter iOS release
- Configure Android signing with a keystore and Gradle signingConfigs for release builds
- Export an iOS .p12 certificate for CI/CD pipelines and store it securely
- Create App ID and provisioning profiles for App Store distribution
- Set up a CI/CD workflow (e.g., GitHub Actions) to sign Flutter releases automatically