TestFlight: internal vs external testing, review, and the 90-day expiry

The difference that matters: internal testers are App Store Connect users on your team, capped at 100, and they get builds without Beta App Review. External testers are anyone with an email address or a public link, capped at 10,000 per app, and the first build you send them goes to review.

Everything else about TestFlight follows from that split, including the 90-day build expiry, which applies either way.

Internal testing

An internal tester is not a person you invite by email in the ordinary sense. It is an existing App Store Connect user with access to your content, which means they already occupy a seat on your team. Apple's limit is "up to 100 internal testers (App Store Connect users with access to your content)".

Because those people already have access to your unreleased work, Apple does not review builds before internal testers can install them. That is the whole value of internal testing: a build that finishes processing is installable in minutes.

You need to be Account Holder, Admin, App Manager, Developer or Marketing to add internal testers, so a developer can set this up without escalating to an Admin.

Builds uploaded from Xcode or Xcode Cloud can be marked as TestFlight Internal Only, and the build shows as internal under the build number. That flag exists so an experimental build cannot accidentally reach external testers.

Use internal testing for: does it install, does it launch, does the new screen work, is this build worth showing anyone.

External testing

External testers are everyone else: your beta list, your customers, a Slack community, whoever clicks a link. Up to 10,000 per app.

The trade is Beta App Review. Apple's wording: "When you add the first build of your app to a group, the build gets sent to App Review to make sure it follows the App Review Guidelines. A review is required only for the first build. Subsequent builds may not require a full review."

Read that carefully, because it is the source of most confusion. It is not one review per app forever, and it is not a review of every build. It is triggered by adding the first build to a group, and later builds "may not" need a full review, which is Apple declining to promise. In practice a new version number, or a build that changes what the app does, is more likely to be looked at again than a bug-fix build on a version already reviewed.

There is also a throughput cap: you can submit up to six builds for TestFlight App Review in a 24-hour period.

Use external testing for: anything you want feedback on from people who do not work for you.

What Beta App Review actually checks

Beta App Review is a lighter pass than App Review, not a different set of rules. It is checking that the build does not violate the App Review Guidelines, which means the categories that come up are the obvious ones: broken functionality, missing content, crashes on launch, private API use, and anything a reviewer cannot get into.

The metadata you supply for it is the Test Information section, and the fields are:

  • Beta App Description, and the What to Test text you attach when adding a build to a group
  • Feedback Email
  • Contact Information
  • Beta App Review Information
  • Demo Account credentials, if the app has a login

The demo account is the single biggest cause of avoidable beta rejections. If your app opens on a sign-in screen and you have not given working credentials, the reviewer sees a sign-in screen. That is the whole review.

If the build is rejected, the build status shows Rejected and the detail appears under App Review in the sidebar. This is a separate track from an App Store submission rejection, so a beta rejection does not affect a version already in review.

The 90-day expiry

Apple states it twice, in two ways: "You can test a build for up to 90 days" and "Your build becomes unavailable for testers after 90 days."

The clock runs from upload, not from when you added the build to a group. So a build you sat on for three weeks before sending to testers gives them 69 days, not 90. Testers already running the app can keep using it until expiry, and then it stops opening.

This has three practical consequences.

A long beta needs a build cadence, not a build. If you plan to test for four months, you are shipping at least two builds regardless of whether the code changed.

Expiry is not a rejection. When testers report "the app stopped working", check the upload date before you look for a bug.

Version numbers matter more than you think. Testers on an expired build need a new build, and TestFlight will show them the newest one available to their group. Keeping one current build per group is simpler than curating several.

Groups

Groups are the unit of distribution, and they are how you avoid sending an unfinished build to your entire list.

A group holds testers and builds. You add one build at a time to a group, and you can add more later from the add button next to Builds on the group page. Internal groups contain internal testers, external groups contain external testers, and the review requirement follows the group type.

A sensible layout for a small team:

  • Internal for the team, every build
  • Trusted external for a handful of friendly users who will tolerate a rough build
  • Public for the wide list, only builds you have some confidence in

The reason to split the external side in two is that Beta App Review applies per group's first build, and a smaller trusted group is a place to catch the embarrassing problem before ten thousand people see it.

Public links

A public link lets anyone install without you collecting their email first. On creating one you choose between Open to Anyone and Filter by Criteria, and you can optionally set a tester limit anywhere from 1 to 10,000.

Filtering by criteria lets you add device and OS requirements, with options including From, Newer than or equal to, Older than or equal to, and All supported for a platform. That is genuinely useful: if your build requires iOS 18, filtering stops a stream of "it will not install" messages from people on older devices.

Set a limit. A public link with no limit posted anywhere findable will fill your 10,000 seats with people who install once and never open the app again, and those seats are not trivially reclaimable in bulk.

The friction nobody warns you about

Processing is not instant, and it is silent. A build you just uploaded is not selectable until Apple finishes processing it. There is no progress bar worth watching.

Testers need the TestFlight app first. An invite link opens in TestFlight, and a tester who does not have it installed sees a confusing dead end. Tell people to install TestFlight before you send the link.

Feedback arrives in two places. Screenshot feedback and crash reports come through TestFlight itself, but only if the tester opts in. Your Feedback Email gets the rest. Neither is a bug tracker, and neither notifies you loudly.

Build numbers must always increase. Reusing a build number for the same version string is rejected at upload. Automate the increment or you will do it by hand at the worst moment.

Export compliance is asked again for beta builds. TestFlight has its own Provide Export Compliance Information prompt. Setting ITSAppUsesNonExemptEncryption in Info.plist stops it recurring on every upload.

Testers leaving is not automatic. Removing someone from a group stops future builds reaching them; it does not uninstall what they have.

Internal seats cost you team seats. Because internal testers are App Store Connect users, adding twenty internal testers means adding twenty people to your team with whatever role you gave them. Give them the narrowest role that lets them test.

Choosing between them, in one line each

Use internal when you need the build in someone's hands today and that someone can be given a seat on the team.

Use external when you need real users, and accept that the first build to each group is subject to review and that no build outlives 90 days.

If you are running a beta longer than a quarter, put a recurring reminder in the calendar at day 75. That single reminder prevents the most common TestFlight failure, which is not a rejection or a limit but simply a build quietly expiring while you assumed testing was still running.

AppSubmit tracks build expiry dates alongside submission state, so day 75 shows up as a warning rather than as a tester email, but a calendar reminder does the same job for free.

Read next

Last reviewed 2026-08-17. Apple and Google change their rules without notice, so check anything decision-critical against their live documentation.