Documentation

TestFlight

Ship a Release IPA to App Store Connect so the build shows up in TestFlight. Local device install stays on device.md. Full path through App Review and release: app-store.md.

Nothing in the toolchain hardcodes your Team ID, Apple ID, or .p8 path — those come from walnut device login, the app manifest, or env vars (WALNUT_DEVELOPMENT_TEAM, WALNUT_ASC_*).

First-try checklist

Do these once per Apple team / bundle ID, then walnut testflight should work without one-off fixes.

#Do thisWhy
1Paid Apple Developer ProgramFree teams cannot distribute to TestFlight
2Register an App ID for your bundle_id (Certificates, Identifiers & Profiles)Automatic signing needs it
3Create an app in App Store Connect with that same bundle IDUpload target
4Create a Team App Store Connect API key (see below)Individual keys fail export with a fake “password incorrect” / -20101
5walnut device login <app> — pick team + paste Team keySaves team + key into the manifest (or use WALNUT_* env)
6App has App Store–ready ios/ (icons + orientations)walnut new installs this; testflight also auto-heals missing AppIcon assets

Then:

   # if you changed the walnut CLI / UIKit

walnut testflight --preflight .   # shell + key check
walnut testflight .

Dry-run IPA (no upload):

walnut testflight . --ipa-only
# → ios/build/walnut-export/*.ipa

Team API key (required)

In App Store Connect:

  1. Users and Access → Integrations → App Store Connect API
  2. Open Team Keys (not Individual)
  3. Generate with Access Admin or App Manager
  4. Download AuthKey_<KEYID>.p8 — keep that name
  5. Issuer ID = the UUID shown at the top of the Keys page (shared by the org). It is not the Key ID.

walnut testflight copies the .p8 to ~/.appstoreconnect/private_keys/AuthKey_<KEYID>.p8 for xcodebuild / altool.

Manifest / envMeaning
authentication_key_path / WALNUT_ASC_KEY_PATHPath to AuthKey_….p8
authentication_key_id / WALNUT_ASC_KEY_IDKey ID
authentication_key_issuer_id / WALNUT_ASC_ISSUER_IDIssuer ID (top-of-page UUID)

What walnut testflight does

  1. Preflight — ensure AppIcon assets (install from Templates/ios/Assets.xcassets if missing); validate ios/project.yml (AppIcon setting, orientations / UIRequiresFullScreen, Main.o linked only via OTHER_LDFLAGS); optionally verify the ASC key is readable via altool --generate-jwt
  2. Compile Walnut for the device LLVM triple
  3. Bump build_number / CFBundleVersion (default; --no-build-bump to skip)
  4. xcodebuild archive (Release, Automatic signing — do not force a Distribution identity by hand)
  5. Export IPA (ExportOptions.plist method app-store-connect)
  6. Upload with xcrun altool --upload-app

Processing in App Store Connect / TestFlight is async (often a few minutes).

Versions

In walnut.toml:

[ios]
marketing_version = "1.0"
build_number = "12"
# false (default) → Info.plist ITSAppUsesNonExemptEncryption = false
# skips App Store Connect “App Encryption Documentation” each upload.
# Set true only if you use non-exempt encryption (then answer ASC docs).
uses_non_exempt_encryption = false

JSON manifests use marketingVersion / buildNumber / usesNonExemptEncryption. Each successful run increments build_number unless you pass --no-build-bump.

walnut testflight syncs the encryption flag into ios/Info.plist (and project.yml when present) before archive — same idea Fastlane often sets via that plist key, without needing Fastlane.

Flags

FlagRole
--preflightCheck ios/ + ASC key; do not archive
--team IDTeam ID override
--account emailPrefer a team linked to this Apple ID
--ipa-onlyStop after IPA export
--no-build-bumpKeep current build number
--bump-buildExplicit bump (default)

New apps

walnut new writes an App Store–ready shell:

  • ios/Assets.xcassets AppIcon set (placeholder icons including 1024 marketing)
  • ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
  • UIRequiresFullScreen + all four interface orientations
  • ITSAppUsesNonExemptEncryption: false (override via [ios] uses_non_exempt_encryption)
  • Main.o linked only via OTHER_LDFLAGS (never also as a frameworks: entry)

Older apps missing icons get them auto-installed on the next testflight run. If project.yml is still missing the settings above, preflight fails with a pointed error — align with the toolchain / the Counter example project.yml.

Edition (Community / Commercial)

Same as simulator/device: a valid ~/.walnut/license selects Commercial (-DWALNUT_COMMERCIAL); otherwise Community (splash + credits). TestFlight does not require Commercial — Community builds may upload with branding.

Troubleshooting

SymptomLikely fix
Export: ASC -20101 / HTTP 401 / “password entered incorrectly”Team key, not Individual. Issuer ID = top-of-page UUID. File as AuthKey_<KEYID>.p8. Re-run walnut device login
“No profiles” right after -20101Red herring — fix API key auth first, then App ID / ASC app
Missing app icon / CFBundleIconNameios/Assets.xcassets + ASSETCATALOG_COMPILER_APPICON_NAME (auto-healed / walnut new)
Orientations / iPad multitasking (90474)UIRequiresFullScreen: true + four orientations in project.yml
Duplicate Main.o symbolsLink only via OTHER_LDFLAGS, not frameworks:
Stale Apple ID in keychain logsHarmless if ASC Team key works; set development_account via login
ASC asks encryption questions every buildDefault uses_non_exempt_encryption = false writes ITSAppUsesNonExemptEncryption; set true only if you need ASC docs
“Version already used”Let the default bump run
altool upload failedKey role; ASC app exists; export compliance; or Transporter

Non-goals (v1)

  • Creating App Store Connect app records via API (bundle id must already exist)
  • App Review Submit for Review / Release buttons (walkthrough: app-store.md; listing assets: screenshots.md walnut store upload)
  • Ad Hoc / Enterprise export