Skip to content

TestFlight Upload

This recipe demonstrates how to build your iOS app and upload it directly to TestFlight for beta testing. It includes signing setup, archive creation, and automated upload to Apple’s TestFlight service.

Using Fastlane (recommended, handles signing automatically):

name: TestFlight Deploy
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Install gems
run: bundle install
- name: Install pods
run: pod install
- name: Build and deploy
run: fastlane beta

Complete YAML with Manual Archive & Export

Section titled “Complete YAML with Manual Archive & Export”

If you prefer manual xcodebuild commands with auto-generated ExportOptions.plist:

name: TestFlight Deploy Manual
platform: ios
environment:
xcode: "16.4"
triggers:
- push
steps:
- name: Install pods
run: pod install
- name: Create Archive
run: |
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-archivePath $PWD/build/MyApp.xcarchive \
-destination 'generic/platform=iOS'
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath $PWD/build/MyApp.xcarchive \
-exportPath $PWD/build \
-exportOptionsPlist $RH_EXPORT_OPTIONS_PLIST
- name: Upload to TestFlight
run: |
xcrun altool --upload-app \
--file $PWD/build/MyApp.ipa \
--type ios \
--apiKey $APP_STORE_CONNECT_API_KEY_ID \
--apiIssuer $APP_STORE_CONNECT_API_ISSUER_ID

The $RH_EXPORT_OPTIONS_PLIST variable is automatically generated by RunnerHub when auto-sign is enabled, eliminating the need to manually create or maintain an ExportOptions.plist file.

You must configure the following environment variables for TestFlight deployment:

  • APP_STORE_CONNECT_API_KEY — Your App Store Connect API key (base64 encoded)
  • APP_STORE_CONNECT_ISSUER_ID — Your issuer ID from App Store Connect

Your fastlane/Fastfile should include a beta lane:

default_platform(:ios)
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
configuration: "Release",
derived_data_path: "build/DerivedData",
destination: "generic/platform=iOS",
archive_path: "build/MyApp.xcarchive"
)
upload_to_testflight(
api_key_path: ENV["APP_STORE_CONNECT_API_KEY"],
skip_waiting_for_build_processing: true
)
end
end