Three Ways to Create an iOS Release Build (Expo React Native)


When it’s time to ship your Expo React Native app to TestFlight or the App Store, there are multiple ways to produce that release .ipa. Each has trade-offs around convenience, automation, and infrastructure. Here’s a rundown of the three main approaches.


Prerequisites (All Methods)

  • An Apple Developer account with a valid provisioning profile
  • An Expo project with native code generated (npx expo prebuild)
  • Production app.json config (correct bundleIdentifier, googleServicesFile, etc.)



Method 1: Xcode GUI (Archive & Organizer)

The most visual and beginner-friendly approach. You do everything through Xcode’s interface.


Open the workspace:

open ios/YourApp.xcworkspace

Archive the app:

  • Select a generic iOS device as the build destination (not a simulator).
  • Go to ProductArchive.
  • Wait for the build to finish.

Export / Upload:

  • When the archive completes, WindowOrganizer opens automatically.
  • Select the archive and click Distribute App.
  • Choose App Store Connect to upload directly to TestFlight, or CustomDevelopment / Ad Hoc for local .ipa export.

Xcode handles code signing automatically if your Apple Developer account is configured. This is the easiest path when you’re building on your own machine and don’t need CI.


When to use: First-time deployments, debugging signing issues, one-off builds where you want visual confirmation of what’s happening.




Method 2: xcodebuild CLI

Same result as Method 1, but scriptable. This is what you want when you’re building from the terminal or in a CI pipeline.


Step 1 — Archive:

xcodebuild archive \
  -workspace ios/YourApp.xcworkspace \
  -scheme YourApp \
  -configuration Release \
  -archivePath ./build/YourApp.xcarchive \
  -destination 'generic/platform=iOS' \
  -allowProvisioningUpdates

This produces an .xcarchive bundle — the same thing Xcode creates when you do Product → Archive.


Step 2 — Export the .ipa:

xcodebuild -exportArchive \
  -archivePath ./build/YourApp.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath ./build/ipa \
  -allowProvisioningUpdates

The ExportOptions.plist tells xcodebuild how to sign and package the archive. Here’s a minimal one for App Store distribution:

<?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-connect</string>
    <key>signingStyle</key>
    <string>automatic</string>
    <key>teamID</key>
    <string>YOUR_TEAM_ID</string>
</dict>
</plist>

The method key controls what kind of build you get:

methodUse case
app-store-connectUpload to App Store Connect / TestFlight
ad-hocDistribute to registered devices directly
developmentDebug builds for registered devices
enterpriseIn-house distribution (requires Enterprise account)

Step 3 (optional) — Upload to App Store Connect:

xcrun altool --upload-app \
  --type ios \
  --file ./build/ipa/YourApp.ipa \
  --apiKey YOUR_API_KEY_ID \
  --apiIssuer YOUR_ISSUER_ID

The --apiKey and --apiIssuer flags use an App Store Connect API key — you can create one in App Store Connect under Users and Access → Integrations → App Store Connect API. Place the .p8 key file in ~/.private_keys/ or ~/.appstoreconnect/private_keys/.


Tip: You can get your ExportOptions.plist by doing a manual Xcode archive first. After exporting through the Organizer, Xcode drops an ExportOptions.plist in the output folder alongside the .ipa. Use that as your template.


When to use: CI/CD pipelines, scripted builds, or when you want reproducible builds without opening Xcode.




Troubleshooting


”No profiles were found” error during archive

If the xcodebuild archive command fails with:

No profiles for 'com.yourapp' were found: Xcode couldn't find any iOS App Development
provisioning profiles matching 'com.yourapp'. Automatic signing is disabled and unable
to generate a profile. To enable automatic signing, pass -allowProvisioningUpdates to
xcodebuild.

This happens when Xcode can’t automatically manage provisioning profiles from the CLI. The fix is to pass -allowProvisioningUpdates to both the archive and exportArchive commands — this flag lets xcodebuild communicate with Apple’s developer portal to create or update profiles automatically. The commands in this post already include this flag.


”app-store” method name is deprecated

When exporting with method: app-store in ExportOptions.plist, you may see:

Command line name "app-store" is deprecated. Use "app-store-connect" instead.

Update your ExportOptions.plist to use app-store-connect as the method value. The export still succeeds with the old name, but app-store-connect is the current canonical name.


No App Store Connect API key for CLI upload

If you don’t have an API key set up for xcrun altool --upload-app, you can upload the .ipa manually using the Transporter app on your Mac:

open -a Transporter build/ipa/YourApp.ipa

This is often simpler for one-off uploads — just sign in with your Apple ID and drag the .ipa in.




Method 3: EAS Build

Expo’s cloud build service. The build runs on Expo’s servers, so you don’t need a Mac or Xcode installed locally.


Install and configure:

npm install -g eas-cli
eas login
eas build:configure

Set up credentials:

eas credentials --platform ios

EAS can manage your provisioning profiles and certificates automatically, or you can provide your own.


Run the build:

eas build --platform ios --profile production

Your eas.json should have a production profile:

{
  "build": {
    "production": {
      "autoIncrement": true
    }
  }
}

Production profiles default to Release configuration and store distribution, so a minimal config is all you need. Add platform-specific overrides under ios only if you need them.


Once the build finishes, EAS gives you a download link to the .ipa. You can then submit it:

eas submit --platform ios

When to use: Teams without Mac hardware, projects that don’t need local native build tooling, or when you want managed code signing without dealing with certificates yourself.


Trade-offs:

  • Free tier has limited build minutes and queue times can be long.
  • Monorepo setups may hit package resolution issues since EAS builds in an isolated environment.
  • You give up control over the exact Xcode version and build environment (though you can pin some settings).



Quick Comparison

Xcode GUIxcodebuild CLIEAS Build
Needs a MacYesYesNo
Needs XcodeYesYesNo
Scriptable / CI-readyNoYesYes
Code signingAutomatic (Xcode)Manual (ExportOptions.plist)Managed or manual
Build speedLocal hardwareLocal hardwareCloud (queue + build)
CostFreeFreeFree tier limited
Monorepo-friendlyYesYesCan be tricky

Which One Should You Pick?

For most solo developers or small teams with a Mac, Method 2 (xcodebuild CLI) hits the sweet spot — it’s scriptable, free, and you keep full control. Wrap it in a shell script and you’ve got a one-command deploy.


If you’re just getting started or debugging a signing issue, Method 1 (Xcode GUI) lets you see exactly what’s happening at each step.


If your team doesn’t have Mac infrastructure or you want zero local build tooling, EAS Build is the managed path — just be aware of the trade-offs with build queues and monorepo support.