How to Ship a Flutter App to TestFlight Without Touching Xcode
Build, sign, and ship Flutter iOS apps to TestFlight from the command line. Skip Xcode entirely after initial setup.
How to Ship a Flutter App to TestFlight Without Touching Xcode
Quick answer: Run flutter build ipa, then upload the IPA with xcrun altool and an App Store Connect API key. Or use a tool that does both steps in one command. Both paths skip the Xcode Organizer.
Flutter's release docs end at "now use Xcode to upload." That's the part most Flutter developers actually dread. This article shows the two ways to skip it.
Prerequisites
You'll need:
- macOS with Xcode installed (Flutter still needs Xcode underneath for iOS, even if you never open the GUI)
- A working Flutter project that builds for iOS (
flutter build iossucceeds) - A bundle ID registered in Apple Developer Portal
- An App Store Connect API key (
.p8file, Issuer ID, Key ID)
If you don't have the API key yet, see App Store Connect API Key Setup first.
Path 1: Vanilla Flutter + altool
This is the no-extra-tools route. Three commands, plus an ExportOptions.plist you write once.
Step 1: Build the IPA
From your Flutter project root:
flutter build ipa --release \
--export-options-plist=ios/ExportOptions.plist
A minimal ios/ExportOptions.plist:
<?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</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
<key>destination</key>
<string>export</string>
</dict>
</plist>
After this completes (~3-6 minutes for a real app), the IPA will be at build/ios/ipa/.
Step 2: Upload to TestFlight
Save your .p8 to ~/.appstoreconnect/private_keys/AuthKey_, then:
xcrun altool --upload-app \
--type ios \
--file build/ios/ipa/YourApp.ipa \
--apiKey ABCD1234EF \
--apiIssuer 12345678-1234-1234-1234-123456789012
altool validates the IPA and uploads. Apple then processes the build (5-15 minutes typically), and it shows up in TestFlight when ready.
Step 3: (Optional) Bump the build number
You'll need a higher build number on every upload. Flutter uses the +N suffix in version: 1.0.0+12 (in pubspec.yaml). Bump that before each flutter build ipa.
Path 2: One Command via MySigner
If the above looks like three steps too many, MySigner's CLI runs the whole pipeline in one command and handles auto-bumping, polling Apple's processing, and submitting.
One-time setup
gem install mysigner
mysigner onboard
The wizard asks for your ASC API key (upload the .p8 once, encrypted at rest with AES-256). It auto-detects Flutter from pubspec.yaml, so there's no Fastfile-style config.
Ship
From your Flutter project root:
mysigner ship testflight
What happens:
- Auto-detects Flutter (reads
pubspec.yaml+ios/Runner.xcworkspace) - Runs
flutter build ipawith the right export options - Auto-bumps
CFBundleVersion - Uploads via the App Store Connect REST API
- Polls Apple's processing every 3 minutes
- Reports when the build is ready for testers
The whole pipeline typically completes in 5-12 minutes (build is the slow part, not the upload).
Free tier ships to TestFlight unlimited, no card.
Common Flutter Shipping Errors
Error (Xcode): No profiles for 'com.example.app' were found
Your bundle ID isn't registered or your team doesn't have a matching provisioning profile. Open Xcode once, sign into your team in Settings → Accounts, and let automatic signing fetch profiles. After that, you can go back to CLI-only.
Could not build the application for the simulator. Error launching application on iPhone
You're running flutter run instead of flutter build ipa. The simulator build doesn't produce an IPA. Use build ipa for shipping.
Could not parse iOS App Bundle's Info.plist
flutter build ios produces an .app bundle (good for sim), but the IPA needs an archive. Use flutter build ipa --release instead.
error: exportArchive: No profiles for 'com.example.app' were found
This bites users who switched away from automatic signing. Either go back to automatic signing in ios/Runner.xcodeproj or specify provisioning profiles in your ExportOptions.plist under the provisioningProfiles key.
Asset validation failed: Invalid Bundle. The bundle does not support arm64
You built for the simulator (x86_64) instead of a real device (arm64). flutter build ipa --release produces arm64 by default; if you specified a --target-platform, remove it.
Apple is still processing
Normal. Apple takes 5-15 minutes to process a TestFlight build after upload. Wait, or use the MySigner CLI which polls automatically.
What About CI?
Both paths work in CI:
- Vanilla path: install
flutter, installxcode(or use a macOS runner that has it), set ASC API key as a base64 secret, run the three commands. - MySigner path: install with
gem install mysigner, setMYSIGNER_API_TOKEN,MYSIGNER_ORG_ID, andMYSIGNER_API_URLas CI secrets, runmysigner ship testflight.
The MySigner path needs fewer secrets in your CI (one token vs Issuer ID + Key ID + .p8 file), and the mysigner doctor command works as a one-line smoke test before you trust a release pipeline.
What This Doesn't Cover
- Android side of Flutter shipping. Different pipeline; see How to upload to Play Store from command line for the equivalent.
- Code signing setup. If your team hasn't ever shipped to TestFlight before, you'll need to set up certificates and profiles in Apple Developer Portal once. After that, the CLI flow works.
- TestFlight beta groups and external testers. Those are configured in App Store Connect; the CLI just uploads builds.
TL;DR
Flutter to TestFlight without Xcode is either two commands (flutter build ipa + xcrun altool) or one (mysigner ship testflight). The IPA produced is identical either way; the difference is how much setup you maintain.