●PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16●VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to miss●EXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration plan●APPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spent●EXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInput●EAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates●PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16●VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to miss●EXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration plan●APPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spent●EXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInput●EAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Beta Testing Your Rork App with TestFlight: from Internal Testers to Public Launch
A practical walkthrough for shipping a Rork or Rork Max app to TestFlight, covering both the EAS Submit route and the Xcode Organizer route. Includes export compliance, internal (100) and external (10,000) tester limits, and the 90-day build expiry that quietly breaks long betas.
The first thing that tripped me up on TestFlight wasn't code signing or review. It was silence.
The upload finished with a green checkmark. The build showed up in App Store Connect with the right number next to it. And the testers I'd invited saw nothing at all.
The cause was a small yellow "Missing Compliance" label sitting next to the build number. Until you answer the export compliance question, that build goes to nobody. It isn't an error, so nothing tells you — you have to go looking.
That turns out to be the pattern with TestFlight. The hard parts aren't the steps that fail loudly; they're the ones that fail silently. This guide walks the full path from a Rork export to testers on real devices, and stops at each of those quiet traps along the way. The examples assume a Rork or Rork Max project, but the mechanics apply to any Expo-based app.
Internal vs. external: what actually changes
TestFlight has two distribution paths with very different characteristics. Picking the wrong one costs you days.
Property
Internal testing
External testing
Tester limit
100
10,000
Who qualifies
App Store Connect users only (Account Holder, Admin, App Manager, Developer, or Marketing role)
Anyone with an email address or the public link
Apple review
Not required
Required for the first build of each version (typically 1–2 days)
Time to distribution
Minutes after processing completes
After review passes
Best for
Your own device checks and fast iteration with collaborators
Real-world conditions and pre-launch signal
You'll still find plenty of sources citing 25 internal testers. That number is out of date — the current limit is 100, with up to 30 devices per tester. At solo-developer scale you will never hit the ceiling, so the real question is simply whether you're willing to add collaborators as App Store Connect users.
My own rhythm: everything stays on internal testing while I'm iterating, and external testing happens once, when a release candidate is genuinely close. Because every version's first external build goes through review, pushing small fixes externally just stacks up waiting time.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to choose between the EAS Submit route and the Xcode Organizer route — Expo-based Rork projects can skip certificate work almost entirely
✦The one-line app.json fix for export compliance, the most common reason an uploaded build never reaches your testers
✦Correct tester limits (100 internal, 10,000 external) and how to plan a beta around TestFlight's 90-day build expiry
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Three things: an Apple Developer Program membership ($99/year), a Rork Max subscription if you're generating a native iOS app, and — maybe — a Mac.
Whether you need the Mac depends on the route you choose. This is the first real decision.
EAS Submit route
Xcode Organizer route
Mac required
No (cloud builds)
Yes
Certificates and profiles
Generated and renewed automatically by EAS
Created manually and registered in Xcode
Good fit when
Staying on Expo, wiring up CI, or working without a Mac
You're hand-editing native code or need fine control over Xcode settings
First-run time
Roughly 20–40 minutes, mostly build queue
Roughly 60–90 minutes, mostly certificate work
Rork's generated projects usually retain their Expo structure, which makes the EAS route meaningfully lighter — it removes the code signing step where most people stall. The sections below follow the EAS path, with Xcode differences noted where they matter.
Step 1: Create the app record in App Store Connect
Only one field here is permanent: the Bundle ID. Platform, name, and SKU can all be adjusted later. Get the Bundle ID wrong and you're creating a fresh app record.
For a Rork Max export, it lives in app.json (or app.config.js) under ios.bundleIdentifier. If you exported a native project instead, check CFBundleIdentifier in Info.plist.
ascAppId is the numeric ID in your App Store Connect URL (the .../apps/1234567890/... segment). From there it's two commands:
# Build — EAS generates credentials interactively if none existeas build --platform ios --profile production# Ship the most recent build to App Store Connecteas submit --platform ios --latest
The first run asks for your Apple ID and an app-specific password. If two-factor authentication is on — and it should be — your regular password won't work, so generate one at appleid.apple.com beforehand to avoid stalling mid-flow.
The Xcode route
Export from Rork Max via Export → Xcode Project and open the .xcodeproj. Under your target's Signing & Capabilities tab, select your Team and confirm the Bundle Identifier matches App Store Connect.
Set the device selector to "Any iOS Device (arm64)", then Product → Archive. When Organizer opens, choose Distribute App → App Store Connect → Upload, and leave signing on "Automatically manage signing" unless you have a reason not to.
If you're creating certificates by hand: Xcode → Settings → Accounts → Manage Certificates for the Apple Distribution certificate, then Certificates, Identifiers & Profiles → Profiles at developer.apple.com/account for the App Store provisioning profile.
Export compliance — where most builds get stuck
This is the trap from the opening. App Store Connect requires a declaration about whether your app uses non-exempt encryption, and until it's answered, the build reaches no one.
Answering it in the dashboard every upload is easy to forget, so declare it permanently in app.json:
Read the question carefully: it asks about non-exempt encryption, not encryption in general. HTTPS traffic and Apple's own platform crypto APIs are exempt, which is why most apps are correctly false.
Set it to true if you ship custom or proprietary encryption — a messaging app with its own end-to-end protocol, for instance. Expect follow-up questions and, in most cases, a request for export documentation. If you aren't certain which side you're on, inventory what your app actually uses before you commit to false. This is not a field you want to answer wrong.
Processing after upload usually takes 5–15 minutes, occasionally 30–60 during busy periods.
Step 3: Get the build to testers
Open the TestFlight tab for your app in App Store Connect.
For internal testing, pick a group, enable the build, and add testers. They'll get an email link that opens the TestFlight app. The prerequisite is that each person already exists as an App Store Connect user — invite them under Users and Access first.
For external testing, create an external group, add the build, and hit Submit for Review.
One field matters more than it looks: What to Test. Leaving it blank, or writing "bug fixes," is a plausible way to get bounced back from review. Describe what you want exercised and what's new. It also appears verbatim in the tester's TestFlight app, so it directly shapes the feedback you get back.
External testers can be invited individually by email or through a public link. Public links spread easily, but you lose track of who's in the group and the feedback arrives without context. Start with individual invites.
Step 4: Feedback, and the 90-day clock
TestFlight's real advantage is in-app feedback. Testers can send a screenshot and a note without leaving the app, and it lands under TestFlight → Feedback alongside crash reports.
If crash reports never arrive, check the tester's device rather than your integration: Settings → Privacy & Security → Analytics & Improvements → Share with App Developers has to be on.
Builds expire after 90 days
This one is easy to miss. Every TestFlight build has a 90-day expiry. Past that point testers can't launch it and see "Beta Has Expired" instead.
If you're planning a long beta, treat 90 days as one cycle and schedule a refresh build inside it. The "everyone suddenly lost access" story almost always traces back to this.
Shipping updates
After a fix, increment ios.buildNumber and rebuild. Keep the version string (1.0) fixed and move only the build number — that's the normal TestFlight cadence.
# after bumping buildNumbereas build --platform ios --profile production --auto-submit
--auto-submit chains the upload onto a successful build, which pays for itself during a tight fix cycle. Enabling the new build in TestFlight notifies testers automatically.
Pre-submission checklist
Once the beta has settled, work through these before filing for review.
Crash rate. Check the TestFlight crash reports for your primary flows. Under 1% is the target.
Screen sizes and OS versions. Verify basic operation on a small device (iPhone SE), a current iPhone, and iPad if you support it. Confirm launch on the minimum iOS version you've declared.
Network conditions. Test beyond Wi-Fi — cellular and a throttled connection. Xcode's Network Link Conditioner or the on-device Developer settings will simulate this.
Permission strings. If you use camera, microphone, or location, make sure NSCameraUsageDescription and friends say something specific. Thin placeholder text draws review comments.
Privacy policy and screenshots. Policy URL configured, and screenshots prepared for each required size (6.9", 6.7", and so on).
Triage table
Symptom
Check first
Fix
Upload succeeded but testers see nothing
Export compliance not declared
Look for the yellow warning beside the build. Add `ITSAppUsesNonExemptEncryption` to app.json and rebuild
Build stuck in "Processing"
Apple-side delay
Usually 30–60 minutes, sometimes hours. Wait it out and check [Apple System Status](https://developer.apple.com/system-status/)
App missing from a tester's TestFlight
Apple ID mismatch or pending review
Confirm the invited address matches their Apple ID; for external groups, wait for review
"Beta Has Expired"
90-day build expiry
Upload and enable a fresh build
Crashes happen but no reports arrive
Tester's sharing setting
Ask them to enable Analytics & Improvements → Share with App Developers
Bundle ID rejected on upload
Drift between app.json and App Store Connect
Compare both strings side by side — casing counts
Notes from running several apps
Across a few apps in parallel, the expensive part of TestFlight has never been the technical setup. It's the back-and-forth with testers.
Early on I handed builds out with "let me know if anything seems off." What came back was mostly "feels kind of slow" and "it's confusing" — impressions I couldn't act on. That wasn't the testers' failing. I hadn't told them where to look.
Now the What to Test field holds at most three numbered flows: "1) first launch through setup completion, 2) behavior when opened offline, 3) where you land after dismissing the paywall." The narrower the ask, the more specific the reports.
The other change was how I use internal testing. I used to check the release build on my own device and, if it looked fine, push straight to external. Now every build goes through internal testing first and I install it via TestFlight on my own phone before anything goes out. Behavior that held up in a development build can shift under release-build optimization. It costs a few minutes and it noticeably cut down my first-round external rejections.
Beta windows got shorter too. Long betas don't produce more feedback — reports cluster in the first few days and then go quiet. Cutting at one or two weeks and moving to the next build, with the 90-day expiry in mind, turned out to iterate faster.
Wrapping up
The failure modes in TestFlight skew toward settings that don't announce themselves: an unanswered compliance question, an expired build, an empty What to Test field. None of them raise an error, and all of them stop distribution.
If you're about to push your first build, start by adding ITSAppUsesNonExemptEncryption: false to app.json. One line removes the single most common reason builds go nowhere.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.