iOS TestFlight with fastlane
Wire one-command TestFlight releases into a native iOS project using
fastlane. The result: fastlane beta (or an optional
pnpm ios:testflight alias) archives a Release build, computes the next build
number, signs, exports an IPA, and uploads it to TestFlight — no manual Xcode
archiving and no Apple ID / 2FA prompts.
This skill targets a single app, TestFlight only (not App Store review
submission, not CI). It assumes an XcodeGen-based project by default but works
for a committed .xcodeproj by deleting one line.
Artifacts to copy
Three concrete, working files live in references/ — copy them and change the
marked constants rather than writing from scratch:
references/Fastfile → apps/ios/fastlane/Fastfile. Two lanes: beta
(build + upload) and upload (push an existing IPA). All the hard-won logic
and gotcha comments are inline.
references/Appfile → apps/ios/fastlane/Appfile. App id + team id.
references/env.testflight.local.example → <repo-root>/.env.testflight.local
(gitignored). Holds the secrets.
references/setup-and-gotchas.md is the full guide — read it before running a
release. Load it for App Store Connect API-key creation, signing requirements,
the versioning model, multi-target plist handling, and the gotcha rationale.
Workflow
- Install fastlane via Homebrew, not
gem install — macOS system Ruby is
too old: brew install fastlane.
- Create an App Store Connect API key (Users and Access → Integrations →
App Store Connect API, "App Manager" role). Capture the Key ID, Issuer ID,
and the one-time
.p8 download. Details in references/setup-and-gotchas.md.
- Copy the three artifacts above into place.
apps/ios/ is an example
layout; if the iOS project lives elsewhere, adjust the IOS_DIR/ROOT_DIR
.. depth at the top of the Fastfile so they resolve to the iOS project dir
and the repo root (where .env.testflight.local lives).
- Set the constants in the Fastfile:
SCHEME, BUNDLE_ID, TEAM_ID, and
PLISTS — list one Info.plist per shipping target (app + every embedded
extension). Match Appfile to the same bundle id / team id.
- Keep or delete the
xcodegen generate line — keep for XcodeGen projects,
delete when the .xcodeproj is committed.
- Fill
.env.testflight.local — required: ASC_API_KEY_ID,
ASC_API_ISSUER_ID, ASC_API_KEY_PATH, ASC_APPLE_ID; optional:
TESTFLIGHT_SIGNING_AUTH_MODE (defaults to the apple_id path when unset).
Add the file to .gitignore.
- Dry-run, then release:
fastlane beta skip_upload:true archives and
exports an IPA without publishing; once that succeeds, run fastlane beta.
Key facts the agent must not get wrong
- Build numbering is automatic. The lane stamps
max(latest TestFlight, local plists) + 1 into every listed plist. Never
hand-edit CFBundleVersion; do bump CFBundleShortVersionString (marketing
version) by hand, and keep it identical across all target plists or the lane
aborts.
ASC_APPLE_ID is the numeric app id, not an email. This is the most common
first-run failure.
- Signing uses automatic signing +
-allowProvisioningUpdates; the Mac needs
an Apple Distribution certificate (true if it has archived to the App Store
from Xcode before).
export_method is "app-store" (legacy alias), and signing xcargs must
be passed to both the archive and export steps — both encoded in the
Fastfile, explained in references/setup-and-gotchas.md.
Portability
Keep the skill project-agnostic. The bundle ids, team id, and apps/ios/...
paths in the artifacts are examples — every release-specific value belongs in
the copied Fastfile or the gitignored env file, never hardcoded as a requirement.
Reference files
references/setup-and-gotchas.md — prerequisites, ASC API key, signing,
versioning model, multi-target plists, gotcha rationale, adaptation checklist.
references/Fastfile — the working lanes to copy.
references/Appfile — app id + team id.
references/env.testflight.local.example — secrets template.
1---2name: ios-testflight-fastlane3description: Set up fastlane to build a native iOS app and upload it to TestFlight: App Store Connect API-key auth, automatic build numbering stamped across every target's Info.plist, optional XcodeGen regeneration, gym archive/export, and upload_to_testflight. Use when adding TestFlight distribution to a new iOS project, wiring a `fastlane beta` lane, scripting `xcodebuild` archive/upload, or debugging code-signing, build-number, or export-method failures during an iOS release.4---56# iOS TestFlight with fastlane78Wire one-command TestFlight releases into a native iOS project using9[fastlane](https://fastlane.tools). The result: `fastlane beta` (or an optional10`pnpm ios:testflight` alias) archives a Release build, computes the next build11number, signs, exports an IPA, and uploads it to TestFlight — no manual Xcode12archiving and no Apple ID / 2FA prompts.1314This skill targets a **single app, TestFlight only** (not App Store review15submission, not CI). It assumes an XcodeGen-based project by default but works16for a committed `.xcodeproj` by deleting one line.1718## Artifacts to copy1920Three concrete, working files live in `references/` — copy them and change the21marked constants rather than writing from scratch:2223- **`references/Fastfile`** → `apps/ios/fastlane/Fastfile`. Two lanes: `beta`24 (build + upload) and `upload` (push an existing IPA). All the hard-won logic25 and gotcha comments are inline.26- **`references/Appfile`** → `apps/ios/fastlane/Appfile`. App id + team id.27- **`references/env.testflight.local.example`** → `<repo-root>/.env.testflight.local`28 (gitignored). Holds the secrets.2930`references/setup-and-gotchas.md` is the full guide — read it before running a31release. Load it for App Store Connect API-key creation, signing requirements,32the versioning model, multi-target plist handling, and the gotcha rationale.3334## Workflow35361. **Install fastlane via Homebrew**, not `gem install` — macOS system Ruby is37 too old: `brew install fastlane`.382. **Create an App Store Connect API key** (Users and Access → Integrations →39 App Store Connect API, "App Manager" role). Capture the Key ID, Issuer ID,40 and the one-time `.p8` download. Details in `references/setup-and-gotchas.md`.413. **Copy the three artifacts** above into place. `apps/ios/` is an example42 layout; if the iOS project lives elsewhere, adjust the `IOS_DIR`/`ROOT_DIR`43 `..` depth at the top of the Fastfile so they resolve to the iOS project dir44 and the repo root (where `.env.testflight.local` lives).454. **Set the constants** in the Fastfile: `SCHEME`, `BUNDLE_ID`, `TEAM_ID`, and46 `PLISTS` — list one `Info.plist` per shipping target (app + every embedded47 extension). Match `Appfile` to the same bundle id / team id.485. **Keep or delete the `xcodegen generate` line** — keep for XcodeGen projects,49 delete when the `.xcodeproj` is committed.506. **Fill `.env.testflight.local`** — required: `ASC_API_KEY_ID`,51 `ASC_API_ISSUER_ID`, `ASC_API_KEY_PATH`, `ASC_APPLE_ID`; optional:52 `TESTFLIGHT_SIGNING_AUTH_MODE` (defaults to the `apple_id` path when unset).53 Add the file to `.gitignore`.547. **Dry-run, then release:** `fastlane beta skip_upload:true` archives and55 exports an IPA without publishing; once that succeeds, run `fastlane beta`.5657## Key facts the agent must not get wrong5859- **Build numbering is automatic.** The lane stamps60 `max(latest TestFlight, local plists) + 1` into every listed plist. Never61 hand-edit `CFBundleVersion`; do bump `CFBundleShortVersionString` (marketing62 version) by hand, and keep it identical across all target plists or the lane63 aborts.64- **`ASC_APPLE_ID` is the numeric app id, not an email.** This is the most common65 first-run failure.66- **Signing** uses automatic signing + `-allowProvisioningUpdates`; the Mac needs67 an Apple Distribution certificate (true if it has archived to the App Store68 from Xcode before).69- **`export_method` is `"app-store"`** (legacy alias), and signing `xcargs` must70 be passed to **both** the archive and export steps — both encoded in the71 Fastfile, explained in `references/setup-and-gotchas.md`.7273## Portability7475Keep the skill project-agnostic. The bundle ids, team id, and `apps/ios/...`76paths in the artifacts are **examples** — every release-specific value belongs in77the copied Fastfile or the gitignored env file, never hardcoded as a requirement.7879## Reference files8081- **`references/setup-and-gotchas.md`** — prerequisites, ASC API key, signing,82 versioning model, multi-target plists, gotcha rationale, adaptation checklist.83- **`references/Fastfile`** — the working lanes to copy.84- **`references/Appfile`** — app id + team id.85- **`references/env.testflight.local.example`** — secrets template.