← Articles

TestFlight distribution for Flutter iOS apps

By Charlin Joe · 17 February 2026

TestFlight is Apple's beta testing platform. It lets you distribute your Flutter iOS app to internal testers (up to 25 team members) and external testers (up to 10,000 users) before App Store submission. Here is how to set it up from scratch and automate it with Fastlane.

Prerequisites

  • Apple Developer Program membership ($99/year)
  • App ID created in App Store Connect
  • Provisioning profiles and certificates configured
  • Flutter iOS build working locally

Manual upload via Xcode

For occasional uploads, Xcode Organizer is the simplest path:

# Build archive
flutter build ipa --release

# The IPA is at:
# build/ios/ipa/YourApp.ipa

Then open Xcode → Window → Organizer → Distribute App → App Store Connect → Upload.

After upload (usually 5-30 minutes for processing), go to App Store Connect → TestFlight → Build.

Automated with Fastlane

# fastlane/Fastfile
lane :beta do
  # Ensure clean git state
  ensure_git_status_clean

  # Bump build number
  increment_build_number(xcodeproj: 'ios/Runner.xcodeproj')

  # Build
  build_ios_app(
    scheme: 'Runner',
    workspace: 'ios/Runner.xcworkspace',
    configuration: 'Release',
    export_method: 'app-store',
    export_options: {
      provisioningProfiles: {
        'com.example.myapp' => 'MyApp Distribution'
      }
    },
    output_directory: './build',
    output_name: 'MyApp.ipa',
  )

  # Upload to TestFlight
  upload_to_testflight(
    api_key_path: 'fastlane/app_store_connect_api_key.json',
    skip_waiting_for_build_processing: true,
    ipa: './build/MyApp.ipa',
  )

  # Commit the build number bump
  git_commit(
    path: ['ios/Runner.xcodeproj/project.pbxproj'],
    message: 'chore: bump iOS build number'
  )
end

App Store Connect API key

Using an API key avoids 2FA interruptions in CI:

  1. App Store Connect → Users and Access → Integrations → App Store Connect API
  2. Generate a new key with "App Manager" role
  3. Download the .p8 file (you can only download once)
// fastlane/app_store_connect_api_key.json
{
  "key_id": "ABC123DEF",
  "issuer_id": "12345678-1234-1234-1234-123456789012",
  "key": "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBH...",
  "in_house": false
}

In CI, store the contents as a secret and write the file:

- name: Create API key file
  run: |
    echo '${{ secrets.APP_STORE_CONNECT_API_KEY }}' > fastlane/app_store_connect_api_key.json

Match for certificate management

Fastlane Match stores certificates and provisioning profiles in a private Git repo, making CI certificate management painless:

# Initialize match (first time only)
bundle exec fastlane match init

# Generate/renew distribution certificates
bundle exec fastlane match appstore
lane :beta do
  match(
    type: 'appstore',
    readonly: true, # CI should never regenerate certs
    api_key_path: 'fastlane/app_store_connect_api_key.json',
  )

  build_ios_app(...)
  upload_to_testflight(...)
end

GitHub Actions workflow

name: Beta Distribution

on:
  push:
    branches: [main]

jobs:
  testflight:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.0'
          cache: true

      - name: Flutter pub get
        run: flutter pub get

      - name: Import certificates via Match
        run: bundle exec fastlane match appstore --readonly
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}

      - name: Build and upload to TestFlight
        run: bundle exec fastlane beta
        env:
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY }}

Managing testers

Internal testers (up to 25): must be App Store Connect users. Builds are available immediately without Apple review.

External testers (up to 10,000): can be anyone with an email address. Require a one-time beta review by Apple (~24 hours for the first build of a new app, instant for subsequent builds).

Add testers in App Store Connect → TestFlight → [Select build] → Add Testers.

Expiration

TestFlight builds expire after 90 days. If testers are still using a build after 90 days, they're prompted to update. Plan releases so you're not leaving testers on expiring builds.

Collecting feedback

TestFlight users can send feedback via the TestFlight app. Enable in App Store Connect → TestFlight → Settings → Beta App Review Information → User Feedback.

Common pitfalls

Forgetting to increment build number. TestFlight rejects uploads with the same build number as an existing build. Always increment before uploading.

Using 2FA for CI. Apple's 2FA interrupts CI builds. Use the App Store Connect API key (via api_key_path in Fastlane) to avoid this entirely.

Not using readonly: true in CI Match. Match with write access in CI can corrupt your certificates if multiple CI jobs run simultaneously. Always use readonly: true in CI — regenerate certificates manually.

Sign in to like, dislike, or report.

TestFlight distribution for Flutter iOS apps — ANN Tech