Automating App Store Releases with Fastlane Deliver
Automating App Store Releases with Fastlane Deliver
Manually uploading an iOS app to App Store Connect is a repetitive, error-prone workflow: export the archive, fill in release notes, set the build number, toggle TestFlight groups, and finally click Submit for Review. Fastlane Deliver (the deliver action) automates every one of these steps from the command line, making it trivial to wire the entire process into a CI/CD pipeline triggered by a Git tag or a merge to your release branch.
What Fastlane Deliver Does
The deliver action is Fastlane's official tool for interacting with App Store Connect. It can:
- Upload a signed
.ipafile to App Store Connect using the Transporter protocol - Push localized app metadata (descriptions, keywords, release notes, support URLs)
- Upload screenshots and preview videos for every device size and locale
- Distribute the build to internal or external TestFlight groups automatically
- Submit the build for App Review with a single flag
- Skip submission and only upload, letting a human trigger review from the web UI
.p8 private key file.Authenticating with an App Store Connect API Key
Before calling deliver, you must tell Fastlane which API key to use. The cleanest approach is the app_store_connect_api_key action, which returns a reusable key object you pass to subsequent actions.
Configuring the API Key in a Fastlane Lane
# fastlane/Fastfile
lane :upload_to_store do
# Load the App Store Connect API key from CI environment variables
api_key = app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"], # e.g. "ABCD1234EF"
issuer_id: ENV["ASC_ISSUER_ID"], # UUID from ASC portal
key_content: ENV["ASC_KEY_CONTENT"], # Contents of the .p8 file (base64 or raw)
is_key_content_base64: true,
duration: 1200, # Token TTL in seconds (max 1200)
in_house: false, # true only for Apple Developer Enterprise
)
deliver(
api_key: api_key,
ipa: "build/Runner.ipa",
skip_metadata: false,
skip_screenshots: true,
submit_for_review: false,
automatic_release: false,
force: true, # Skip HTML report preview
precheck_include_in_app_purchases: false,
)
end
.p8 key content in your Fastfile. Store it as a base64-encoded CI secret (e.g. base64 AuthKey_ABCD1234EF.p8) and set is_key_content_base64: true. On GitHub Actions, add it as an encrypted repository secret and reference it via ENV["ASC_KEY_CONTENT"].Distributing to External TestFlight Groups
After the binary passes Apple's processing queue (typically 10–15 minutes), you can automatically add it to an external TestFlight group. Use the pilot action (also known as testflight) or pass distribution parameters directly to deliver.
Full CI Lane: Build, Sign, Upload, and Distribute to TestFlight
# fastlane/Fastfile
lane :beta do
api_key = app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"],
issuer_id: ENV["ASC_ISSUER_ID"],
key_content: ENV["ASC_KEY_CONTENT"],
is_key_content_base64: true,
)
# Increment build number from latest TestFlight build
increment_build_number(
build_number: app_store_build_number(
api_key: api_key,
live: false,
) + 1,
xcodeproj: "ios/Runner.xcodeproj",
)
# Build and sign the Flutter IPA
sh "flutter build ipa --release " \
"--export-options-plist=ios/ExportOptions.plist"
# Upload to TestFlight and distribute to external group
pilot(
api_key: api_key,
ipa: "build/ios/ipa/Runner.ipa",
distribute_external: true,
groups: ["External Beta Testers"],
notify_external_testers: true,
changelog: ENV["RELEASE_NOTES"] || "Bug fixes and improvements.",
beta_app_review_info: {
contact_email: "qa@example.com",
contact_first_name: "QA",
contact_last_name: "Team",
contact_phone: "+1-555-0100",
demo_account_name: "demo@example.com",
demo_account_password: "DemoPass123!",
notes: "Use demo account to test all flows.",
},
skip_waiting_for_build_processing: false,
)
end
Submitting for App Review
To go straight from CI to the App Review queue, set submit_for_review: true inside the deliver call. Combine this with automatic_release: false to keep a human in the loop for the final publish decision, or set it to true for a fully automated release once Apple approves.
submit_for_review: true in CI means every successful build on that lane will enter the App Review queue. Gate this lane behind a protected tag pattern (e.g. v*.*.*-rc) or a manual CI trigger to avoid accidentally submitting development builds for review.Wiring the Lane into a GitHub Actions CI Pipeline
A typical GitHub Actions workflow triggers the release lane when a version tag is pushed. The .p8 content and other secrets are stored as repository secrets and injected at runtime.
GitHub Actions Workflow (.github/workflows/release.yml)
# .github/workflows/release.yml
name: App Store Release
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # triggers on e.g. v1.4.0
jobs:
release:
runs-on: macos-14 # Xcode 15 image
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.22.0"
channel: stable
- name: Install Ruby & Fastlane
run: |
gem install bundler --no-document
bundle install
- name: Install Flutter dependencies
run: flutter pub get
- name: Install CocoaPods
run: cd ios && pod install --repo-update
- name: Run Fastlane beta lane
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
RELEASE_NOTES: "Version ${{ github.ref_name }} released."
run: bundle exec fastlane beta
Fastlane Deliver Metadata Structure
When skip_metadata: false, Fastlane expects a fastlane/metadata/ folder with a specific layout. This lets you version-control all App Store content alongside your code:
metadata/en-US/name.txt— App name for the localemetadata/en-US/description.txt— Full App Store descriptionmetadata/en-US/keywords.txt— Comma-separated keywordsmetadata/en-US/release_notes.txt— What’s new in this versionmetadata/en-US/support_url.txt— Support URLmetadata/review_information/— App Review contact and demo credentials
fastlane deliver init once to download your existing App Store metadata into the correct folder structure. Commit the entire fastlane/metadata/ directory to version control so release notes and descriptions are reviewed in pull requests just like code changes.Summary
Fastlane Deliver transforms the App Store submission process from a manual, multi-step GUI workflow into a single terminal command. The key points to remember are: use an App Store Connect API key (never username/password) stored as CI secrets; use deliver for metadata and binary upload; use pilot for TestFlight distribution with external tester groups; and gate the submit_for_review flag behind protected tags or manual triggers to prevent accidental submissions. With this setup, every Git tag automatically produces a TestFlight build with no manual intervention required.