---
modificationDate: July 21, 2026
title: Distribute an iOS app with TestFlight
description: Learn how to get an iOS build onto testers' devices with TestFlight, and when to use internal versus external TestFlight testing.
---

<AgentInstructions>

## Submitting Feedback

If this page contains errors, outdated information, or gaps that blocked you from completing a task, report it so the docs can be improved:

curl -X POST https://api.expo.dev/v2/feedback/docs-send -H 'Content-Type: application/json' -d '{"url":"/submit/testflight/","feedback":"🤖 Agent feedback for docs: <specific, actionable description> (<model>, <harness>)"}'

Only submit when you have something specific and actionable to report. Replace <model> with the model you are running as and <harness> with the tool you are running in (for example, Claude Code, Cursor, Codex CLI).

## Navigation

When answering a related or follow-up question, fetch the relevant page below as Markdown (.md) instead of guessing; use llms.txt for the full map.

You are here: EAS > EAS Submit
Pages in this section:
- [Submit to Google Play Store](https://docs.expo.dev/submit/android.md)
- [Submit to Apple App Store](https://docs.expo.dev/submit/ios.md)
- [TestFlight](https://docs.expo.dev/submit/testflight.md) (this page)
- [Manual Android submission](https://docs.expo.dev/submit/android-manual.md)
- [Manual iOS submission](https://docs.expo.dev/submit/ios-manual.md)
- [Configure with eas.json](https://docs.expo.dev/submit/eas-json.md)
Full documentation tree: [llms.txt](https://docs.expo.dev/llms.txt)

</AgentInstructions>

This documentation is available as Markdown for AI agents and LLMs. See the [full Markdown index](/llms.txt) or append .md to any documentation URL.

# Distribute an iOS app with TestFlight

Learn how to get an iOS build onto testers' devices with TestFlight, and when to use internal versus external TestFlight testing.

**TestFlight** is Apple's internal and beta app distribution service. After you create an iOS production build, your app can reach testers' devices through TestFlight before the app is released publicly on the App Store.

There are two kinds of testing and the difference decides how quickly your build reaches your app users. **Internal testing** goes to your own App Store Connect team and is available as soon as the build finishes processing. **External testing** reaches anyone else. However, you have to add the build to a group in App Store Connect, and the group's first build must clear Apple's Beta App Review process.

#### Prerequisites

##### Paid Apple Developer account

A paid Apple Developer account is required to submit apps to the Apple App Store. Sign up on the [Apple Developer Portal](https://developer.apple.com/account/).

##### An app record in App Store Connect

`eas submit` creates an app record automatically on App Store Connect when you submit your first build. Create an app by clicking **Create app** in [App Store Connect](https://appstoreconnect.apple.com/). Alternatively, use the [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) to create an app record programmatically or set `ascAppId` in your `submit` profile in **eas.json** to skip creating an app record.

##### Production build

TestFlight only accepts builds with `"distribution": "store"` set in **eas.json**, which is default for an [EAS Build](/build/introduction.md) `production` profile. See [Build a production app](/submit/ios.md#build-a-production-app) for instructions.

##### TestFlight app

Testers need the [TestFlight app](https://apps.apple.com/us/app/testflight/id899247664) installed on their iOS device to install and test your app.

## Quick start

If you have an Apple Developer account and nothing else set up yet, you can create an app record on App Store Connect, build a production **.ipa**, and submit it to TestFlight with a single command:

#### npx testflight

```sh
# npm
npx testflight

# yarn
yarn dlx testflight

# pnpm
pnpm dlx testflight

# bun
bunx testflight
```

The above command creates your credentials, runs a production build on EAS Build, and submits it for internal testing via TestFlight. It is a shorthand for building and submitting in one step with EAS CLI.

#### EAS CLI

```sh
eas build --platform ios --auto-submit
```

The [`--auto-submit`](/build/automate-submissions.md) flag hands the finished build off to EAS Submit automatically. You can also build and submit as separate steps. If you already have a production build, a rebuild isn't necessary. Submit the existing build directly:

```sh
eas submit --platform ios
```

The rest of the guide covers internal and external testing in more detail.

## Internal versus external testing

Before you can publicly release your app on the Apple App Store, you can distribute it to testers through TestFlight in two ways: **Internal testing** and **External testing**. The difference is how quickly your build reaches your testers:

| Purpose | Internal testing | External testing |
| --- | --- | --- |
| Who can test your app | Users on your App Store Connect team | Anyone. Testers do not need an App Store Connect account. |
| Maximum testers allowed | 100 | 10,000 per app |
| Beta App Review | ✗ | **Required** on the first build of every app version |
| How testers are invited | Email only | Email, CSV import, or a shareable public link |
| Beta app description | ✗ | **Required** for external testing. This is the description that appears in TestFlight. |
| Feedback email | ✗ | **Required** for external testing. This is the email that appears in TestFlight. |
| Time to reach testers | Immediately after processing | After Beta App Review approves the build |
| Build expiry | 90 days from upload | 90 days from upload |
| Devices per tester | 30 | 30 |

> **Internal distribution is not internal testing**. Both of these terms sound identical. **EAS** [internal distribution](/build/internal-distribution.md) produces an [ad hoc or enterprise-signed build](/build/internal-distribution.md#overview-of-distribution-mechanisms), which is installed from a URL and are limited to registered unique device identifiers (UDIDs). TestFlight internal testing needs `"distribution": "store"` and goes through Apple.

## Set up internal testing

Internal testing goes to your own App Store Connect team and is available as soon as the build finishes processing.

### Build and submit

To upload a build to App Store Connect for internal testing, ensure you have a production build created. After that, run the following command to submit it to TestFlight:

```sh
eas submit --platform ios
```

### Wait for processing

Apple processes the build before it can be distributed. This usually takes 5 to 10 minutes, though there is no guaranteed time. After processing, Apple notifies you by email.

After your app is processed, log in to [App Store Connect](https://appstoreconnect.apple.com/), select your app, and go to **TestFlight**. You should see the build listed under **iOS Builds**.

### Create an internal testing group

> Skip this step if you already have an internal testing group. You can add testers to an existing group instead.

In App Store Connect, go to **TestFlight** > click the plus (`+`) icon next to **Internal Testing** > enter the name of the **Internal Group** > click **Create**.

Then, click the plus (`+`) icon next to **Testers** to add testers to the group. You can add testers from your App Store Connect team by selecting their emails and clicking **Add**. Testers will receive an email invitation to test your app.

### Invite testers

On the tester's device, tap the link in the invitation email. The app will appear in TestFlight, from where the tester can install it.

> **Tip**: You can also target internal groups directly from the EAS CLI if you have pre-existing internal testing groups. For example, the command `eas submit --platform ios --groups "QA Team" --what-to-test "New onboarding flow"` targets the internal testing group named "QA Team" and sets the "What to Test" description for the build.

## Set up external testing

External testing reaches people outside your App Store Connect team.

> You must create an **internal** group before App Store Connect will let you create an **external** group.

### Create an external group

In **App Store Connect** > **TestFlight** > click the plus (`+`) icon next to **External Testing** > enter the name of the **External Group** > click **Create**.

### Use an existing internal build

You can use an existing internal build for external testing. Click **Add Builds** > under **Select a Build to Test**, select the build you want to use > click **Next**.

### Fill in test information

External testing requires a beta app description and a feedback email before Apple accepts the build for review. If your app has a login, enable **Sign-in Information** and provide a test account for Apple to use. Click **Next**.

### Submit for Beta App Review

Provide the information requested under **What to Test** and click **Submit for Review**. Apple reviews the build and notifies you by email when it is approved.

### Invite testers

Once approved, invite by email, import a CSV, or turn on a public link. A public link can be open to anyone or filtered by device and OS version.

## FAQs

#### Do I need a Mac to distribute an app with TestFlight?

No. Both `eas build` and `eas submit` run on macOS, Linux, and Windows. The build runs on EAS Build infrastructure and EAS Submit uploads the **.ipa** to App Store Connect for you.

#### Do I need a new build to move from internal to external testing?

No. Add the build that is already in App Store Connect to an external group and submit it for Beta App Review. You only need a new build when the app itself changes.

#### Why can't a teammate add a build to an external group?

Their App Store Connect role does not allow it. A **Developer** or **Marketing** role can manage internal testing but not external testing, which requires **Account Holder**, **Admin**, or **App Manager**.

The same restriction applies to credentials. Only an **Account Holder** or **Admin** can create the App Store Connect API key that EAS Submit uses. See [Apple Developer Program roles and permissions](/app-signing/apple-developer-program-roles-and-permissions.md) for the complete matrix.

#### Does a TestFlight build get released on the App Store?

No. Releasing to the App Store is a separate submission that you start manually in App Store Connect. A build in TestFlight stays in TestFlight.

Testing also does not stop when you release. If you do not expire a build before it goes live on the App Store, testers who already have an invitation can keep testing it. To stop them, go to **TestFlight** > select the build > **Expire Build**.

#### Why is my build stuck on Missing Compliance?

App Store Connect is waiting for an answer to the export compliance question and cannot distribute the build to testers until you answer it. Instead of answering it on every upload, answer it once in your app config:

```json
{
  "ios": {
    "config": {
      "usesNonExemptEncryption": false
    }
  }
}
```

Set [`usesNonExemptEncryption`](/versions/latest/config/app.md#usesnonexemptencryption) to `false` only when your app uses no encryption beyond what Apple exempts, such as HTTPS through the operating system. The declaration also covers third-party libraries your app links against, and it applies to TestFlight builds, not only App Store releases.

#### Can I add a build to a TestFlight group from the command line?

Yes, for internal groups. Pass `--groups` with the name of an existing internal group and use `--what-to-test` to set the test notes:

```sh
eas submit --platform ios --groups "QA Team" --what-to-test "New onboarding flow"
```

`--groups` accepts **internal** group names only. To add a build to an external group, use EAS Workflows.

#### Can I automate distributing a build to external groups?

Yes, with the pre-packaged [`testflight` job](/eas/workflows/pre-packaged-jobs.md#testflight) in EAS Workflows. It adds a build to internal and external groups, sets the "What to Test" notes, and submits for Beta App Review:

```yaml
jobs:
  build_ios:
    name: Build iOS
    type: build
    params:
      platform: ios
      profile: production

  testflight:
    name: Distribute to TestFlight
    type: testflight
    needs: [build_ios]
    params:
      build_id: ${{ needs.build_ios.outputs.build_id }}
      internal_groups: ['QA Team']
      external_groups: ['Public Beta']
      changelog: |
        What's new in this release:
        - New features
        - Bug fixes
```

`submit_beta_review` defaults to `true` when `external_groups` is provided. Complete the TestFlight test information in App Store Connect before you run the job with external groups.
