Tech 4 min read

Automate iOS app builds and App Store uploads with Claude Code and the Xcode CLI

IkesanContents

I saw a post on X saying someone used Claude Code to invoke Xcode CLI and automate everything from building an app to publishing it on the App Store. I thought “surely not”, but it looks like it actually works.

I had already tried automation through an MCP server for Android in another article, and if iOS can be handled the same way, development efficiency should improve a lot.

This article summarizes the overall Xcode CLI flow and the main points when calling it from Claude Code.


Overall flow

StepToolExample command
Buildxcodebuildxcodebuild clean build -scheme MyApp
Archivexcodebuildxcodebuild archive -archivePath ./MyApp.xcarchive
Export IPAxcodebuildxcodebuild -exportArchive -exportOptionsPlist options.plist
Install on devicexcrun devicectlxcrun devicectl device install app --device <ID> MyApp.ipa
Upload to App Storexcrun altoolxcrun altool --upload-app -f MyApp.ipa --apiKey <KEY>

Because everything can be done from the CLI, you can tell Claude Code “build it and upload it to the App Store” and, in theory, it can run the whole flow.


Prerequisites

Some things still need to be set up manually. The CLI alone is not enough.

Required environment

  • macOS and Xcode must be installed
  • You must be enrolled in the Apple Developer Program (12,980 yen/year)

Certificates and provisioning profiles

Set them up in Xcode under “Signing & Capabilities”, or create them manually in the Apple Developer Portal.

  • Distribution Certificate
  • App Store Provisioning Profile

If they are not in your keychain, the build may succeed but archiving will fail.

App Store Connect API key

You need an App Store Connect API key for upload automation.

  1. App Store Connect -> Users and Access -> Keys
  2. Create a new key under the “App Store Connect API” tab
  3. Keep the .p8 file, Key ID, and Issuer ID

Build and archive with xcodebuild

Clean the project

xcodebuild clean \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -configuration Release

Create an archive

xcodebuild archive \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -configuration Release \
  -archivePath ./build/MyApp.xcarchive \
  -destination 'generic/platform=iOS'

If you use a workspace (.xcworkspace), change -project to -workspace.

Export the IPA

xcodebuild -exportArchive \
  -archivePath ./build/MyApp.xcarchive \
  -exportOptionsPlist exportOptions.plist \
  -exportPath ./build

Example exportOptions.plist

App Store settings:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>uploadBitcode</key>
    <false/>
    <key>uploadSymbols</key>
    <true/>
    <key>signingStyle</key>
    <string>automatic</string>
</dict>
</plist>

For development use (Ad Hoc), change method to ad-hoc.


Install on a device

iOS 17 and later: xcrun devicectl

From Xcode 15 onward, you can use Apple’s devicectl command.

# List devices
xcrun devicectl list devices

# Install the app
xcrun devicectl device install app \
  --device <DEVICE-ID> \
  MyApp.ipa

# Launch the app
xcrun devicectl device process launch \
  --device <DEVICE-ID> \
  com.example.MyApp

iOS 16 and earlier: ios-deploy

Use the third-party ios-deploy tool.

# Install
brew install ios-deploy

# List devices
ios-deploy -c

# Install and launch the app
ios-deploy -b MyApp.app -d

Note: ios-deploy does not work on iOS 17 and later. Use devicectl for iOS 17+.


Upload to the App Store with altool

Store credentials in the keychain

Saving credentials avoids typing them every time.

Recommended: use an App Store Connect API key

xcrun notarytool store-credentials "my-api-key" \
  --key /path/to/AuthKey_XXXXXXXXXX.p8 \
  --key-id <KEY_ID> \
  --issuer <ISSUER_ID>

If you use an app-specific password

xcrun altool --store-password-in-keychain-item "my-app-password" \
  -u your@email.com \
  -p <APP_SPECIFIC_PASSWORD>

Upload

xcrun altool --upload-app \
  -f ./build/MyApp.ipa \
  --apiKey <KEY_ID> \
  --apiIssuer <ISSUER_ID>

Or:

xcrun altool --upload-app \
  -f ./build/MyApp.ipa \
  -u your@email.com \
  -p @keychain:my-app-password

Validation only

xcrun altool --validate-app \
  -f ./build/MyApp.ipa \
  --apiKey <KEY_ID> \
  --apiIssuer <ISSUER_ID>

Running validation first can catch errors early.


Tips for using it from Claude Code

1. Set environment variables

Putting the API key path and IDs in environment variables makes Claude Code prompts shorter.

# ~/.zshrc
export APPLE_API_KEY_PATH="$HOME/.apple/AuthKey_XXXXXXXX.p8"
export APPLE_API_KEY_ID="XXXXXXXXXX"
export APPLE_API_ISSUER_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

2. Keep exportOptions.plist in the repo

If exportOptions.plist sits in the project root, Claude Code does not have to guess.

3. Script the whole flow

It is annoying to type multiple commands every time, so it helps to wrap them in a shell script.

#!/bin/bash
# scripts/build-and-upload.sh

set -e

xcodebuild clean archive \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -configuration Release \
  -archivePath ./build/MyApp.xcarchive \
  -destination 'generic/platform=iOS'

xcodebuild -exportArchive \
  -archivePath ./build/MyApp.xcarchive \
  -exportOptionsPlist exportOptions.plist \
  -exportPath ./build

xcrun altool --upload-app \
  -f ./build/MyApp.ipa \
  --apiKey $APPLE_API_KEY_ID \
  --apiIssuer $APPLE_API_ISSUER_ID

echo "Upload complete!"

Then you can just tell Claude Code to run scripts/build-and-upload.sh.