Shipping a Flutter app to TestFlight via Xcode Cloud sounds like a checkbox.
In practice it's a gauntlet of undocumented failures — Xcode Cloud doesn't know what
Flutter is, SwiftPM resolution breaks, exports fail for reasons nobody explains, and
the build logs live behind Apple auth where you can't grep them.
This repo is the set of working CI scripts, a CLI to read your Xcode Cloud builds from the terminal, and a gotchas guide that gets a Flutter app building on Xcode Cloud and delivering to TestFlight — including FCM push.
Everything here was distilled from getting a real production Flutter app green on Xcode Cloud. Each fix maps to a specific wall you will hit.
| # | The wall | The fix |
|---|---|---|
| 1 | Xcode Cloud builds, but it has no idea what Flutter is → missing SDK / frameworks / pods | ci_scripts/ci_post_clone.sh installs Flutter + generates the iOS artifacts |
| 2 | Could not resolve package dependencies … 'flutterfire' was added |
Commit Package.resolved AND pin the Flutter version (GOTCHAS #2) |
| 3 | Archive fails: your team has no devices … No profiles for … |
Register one device — Xcode Cloud needs it to build the dev/ad-hoc exports (GOTCHAS #3) |
| 4 | App Store rejects build: Missing Compliance |
ITSAppUsesNonExemptEncryption in Info.plist (GOTCHAS #4) |
| 5 | ITMS-90683: Missing purpose string from a plugin you don't think you use |
Add the purpose strings the SDK references (GOTCHAS #5) |
| 6 | You want FCM push, but the plist is gitignored | Materialize it from a base64 secret + wire it in CI (wire_firebase.rb, GOTCHAS #6) |
| 7 | "Build failed" — but the logs are locked in App Store Connect | tools/asc_ci.py reads them from your terminal |
Full write-up: docs/GOTCHAS.md.
Copy ci_scripts/ into your Flutter project's ios/ folder and make it executable:
cp -r ci_scripts <flutter-project>/ios/ci_scripts
chmod +x <flutter-project>/ios/ci_scripts/ci_post_clone.shXcode Cloud auto-runs any ci_scripts/ci_post_clone.sh next to the Xcode project.
In your workflow → Environment → Environment Variables:
| Name | Required | Notes |
|---|---|---|
FLUTTER_VERSION |
✅ | Pin it (e.g. 3.44.1) — must match your committed Package.resolved |
FLUTTER_PROJECT_PATH |
— | Path to the Flutter project in the repo (default .; e.g. mobile for a monorepo) |
API_BASE_URL |
— | Example --dart-define; add your own |
GOOGLE_SERVICE_INFO_PLIST_B64 |
— | base64 of GoogleService-Info.plist to enable FCM push (mark Secret) |
flutter pub get
cd ios && open Runner.xcworkspace # let Xcode resolve packages, then:
git add ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolvedAnd register one device in the Apple Developer portal (GOTCHA #3).
That's the 90% that gets you green. The rest (compliance, purpose strings, push) is in the gotchas.
GitHub only shows pass/fail; the real logs are in App Store Connect. asc_ci.py
pulls the failing steps + error messages over the App Store Connect API — no
third-party dependencies (it signs the ES256 JWT with openssl + the stdlib).
# one-time: App Store Connect → Users and Access → Integrations → generate an API key
mkdir -p ~/.config/asc-ci && cat > ~/.config/asc-ci/config <<EOF
ASC_ISSUER_ID=<issuer-uuid>
ASC_KEY_ID=<key-id>
ASC_KEY_FILE=~/Downloads/AuthKey_XXXX.p8
ASC_APP_ID=<your-app-apple-id>
EOF
python3 tools/asc_ci.py latest 3 # last 3 builds, with each failing action's errors
python3 tools/asc_ci.py issues <id> # one build action's issues
python3 tools/asc_ci.py get /v1/ciProductsExample output:
=== Build #58 | COMPLETE / FAILED ===
• Archive - iOS [ARCHIVE] FAILED (errors=2, warnings=1)
[ERROR] ValidationStep
Could not resolve package dependencies: a resolved file is required ...
ci_scripts/ci_post_clone.sh Xcode Cloud hook: install Flutter + generate iOS artifacts (+ optional FCM)
ci_scripts/wire_firebase.rb add GoogleService-Info.plist + push entitlement to the Runner target
tools/asc_ci.py read Xcode Cloud build results from the CLI (ASC API, zero deps)
docs/GOTCHAS.md every wall and its fix, in detail
Hit a wall this doesn't cover? PRs and issues welcome — the goal is to be the page you wish you'd found. See CONTRIBUTING.md.
MIT.