TestFlight Distribution
Internal/external testers and build expiration handling - the TestFlight cookbook for shipping Expo SDK 57 IPAs to QA, stakeholders, and beta users before App Store release.
Search across all documentation pages
Internal/external testers and build expiration handling - the TestFlight cookbook for shipping Expo SDK 57 IPAs to QA, stakeholders, and beta users before App Store release.
Quick-reference recipe card - copy-paste ready.
# Build production-shaped IPA for TestFlight
eas build --profile production --platform ios
# Upload to App Store Connect (TestFlight tab)
eas submit --platform ios --latest
# Or build + submit in one workflow (eas.json)
eas build --profile production --platform ios --auto-submit// eas.json - production profile for TestFlight
{
"build": {
"production": {
"distribution": "store",
"ios": {
"simulator": false
}
},
"preview": {
"distribution": "internal",
"ios": {
"simulator": false
}
}
},
"submit": {
"production": {
"ios": {
"appleId": "you@example.com",
"ascAppId": "1234567890",
"appleTeamId": "ABCDE12345"
}
}
}
}When to reach for this:
eas submit to App Review.Step 1 - Configure EAS credentials
eas credentials --platform ios
# Distribution cert + App Store provisioning profile for bundle IDEnsure App Store Connect has an app record matching ios.bundleIdentifier.
Step 2 - Build and submit
eas build --profile production --platform ios
eas submit --platform ios --latestApp Store Connect → TestFlight tab → build shows Processing (5–30 minutes) then Ready to Test.
Step 3 - Internal testing (fast path)
| Property | Internal testers |
|---|---|
| Who | App Store Connect users with Admin/Developer/Marketing/Customer Support role |
| Limit | 100 users |
| Beta App Review | Not required |
| Expiration | 90 days from upload |
| Use for | Engineering, PM, design - daily QA |
Step 4 - External testing (wider beta)
| Property | External testers |
|---|---|
| Who | Email invites or public link |
| Limit | 10,000 testers |
| Beta App Review | Required per build (first external) |
| What Apple checks | Crashes, placeholder content, obvious guideline violations |
| Use for | Beta program, friendly customers, UAT |
Public Beta).Step 5 - Handle 90-day expiration
TestFlight build expires: 2026-04-08
Today: 2026-04-01
Action: schedule eas build this week; notify testers before lapse# Rebuild same git tag or release branch
eas build --profile production --platform ios
eas submit --platform ios --latest
# External groups: new build may need Beta App Review if material changesAutomate reminders:
# .github/workflows/testflight-expiry-reminder.yml (conceptual)
# Parse Connect API for earliest expiring build; Slack when < 14 daysWhat this demonstrates:
| Channel | Profile | Install method | Expires |
|---|---|---|---|
| TestFlight internal | production / store | TestFlight app | 90 days |
| TestFlight external | production / store | TestFlight app | 90 days |
| Ad hoc | preview / internal | Direct IPA / QR (EAS) | 1 year cert |
| Development | development | USB / dev client | 7-day cert (dev) |
| App Store | production | Public store | Until replaced |
For universal links and push, prefer TestFlight (production aps-environment) over dev clients when validating production behavior.
Upload received → Processing → Ready to Test → (External) Waiting for Review → Testing
↓ fail
Invalid Binary / Missing ComplianceMissing Compliance: Answer encryption questions in Connect - same as App Store.
Invalid Binary: Often privacy manifest (ITMS-91053) - fix before testers see anything.
Crash logs and screenshots flow back to Connect → TestFlight → build → Crashes.
Build 1.4.0 (42) - March 2026 beta
Focus:
- Universal link: https://shop.example.com/orders/demo opens Order screen
- Push: enable notifications on login; tap notification routes to inbox
- Account deletion: Settings → Delete Account
Known issues:
- iPad layout minor clipping in cart - fix scheduled 1.4.1
Credentials: review@example.com / BetaDemo2026!# Non-interactive CI submit
EXPO_TOKEN=xxx eas build --profile production --platform ios --non-interactive
EXPO_TOKEN=xxx eas submit --platform ios --latest --non-interactiveGate submit on expo-doctor, tests, and internal TestFlight soak time before external beta.
"ios": { "simulator": true }.app for Simulator - cannot upload to TestFlight.simulator: false).developmentClient: true for TestFlight - wrong binary type for store distribution. Fix: production profile without dev client flag for TestFlight track.aps-environment.ascAppId in eas.json submit profile.appID mismatch with production bundle. Fix: iOS Universal Links.| Alternative | Use When | Don't Use When |
|---|---|---|
| EAS internal distribution (ad hoc) | Quick QR install, no TestFlight | Need 500+ testers or crash analytics in Connect |
| Xcode Organizer upload | Manual one-off without EAS | Team already standardized on eas submit |
| Firebase App Distribution | Cross-platform Android+iOS informal QA | You want Apple-native tester management |
| App Store phased release | Post-approval gradual rollout | Pre-release validation |
production artifact; App Review is a separate gate after TestFlight validation.eas submit over Apple ID password + 2FA.Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 19, 2026