---
title: "Releasing for iOS"
description: "Configure Xcode signing and privacy declarations, build an IPA with the Shorebird CLI, and upload it to App Store Connect so it can receive patches."
---

This page takes a Flutter app from Xcode signing setup to a build on the App Store that can receive Shorebird patches. Steps 1 and 2 are the same one-time setup any Flutter app needs. Step 3 builds the archive and IPA with `shorebird release ios` on a Mac, and step 4 uploads that build to App Store Connect.

Upload the build `shorebird release` made

Patches only install on the exact build `shorebird release ios` produced and uploaded to Shorebird. Don’t upload an IPA from `flutter build ipa`, and don’t use **Product > Archive** in Xcode after `shorebird release` runs. A build made any other way either doesn’t include Shorebird or doesn’t match the release, and no patch will install on it. See [Patches can’t install because the app isn’t the build Shorebird released](/code-push/troubleshooting/#patches-cant-install-because-the-app-isnt-the-build-shorebird-released).

## Step 1: Configure bundle identifier and signing in Xcode

Open the `ios` directory in Xcode:

```
open ios/Runner.xcworkspace
```

1. Select the `Runner` project in the sidebar.
2. Under the **Signing & Capabilities** tab, select your **Team** registered with the [Apple Developer Program](https://developer.apple.com/programs/).
3. Set a unique **Bundle Identifier** (e.g., `com.example.myapp`).
4. Choose **Automatically manage signing** (recommended for standard release flows) or configure custom provisioning profiles.

`shorebird release ios` signs the IPA with these settings. If it stops with a signing or provisioning profile error, see [Signing issues](/code-push/release/#signing-issues).

## Step 2: Privacy manifests and permission descriptions

Apple requires explicit usage descriptions in `ios/Runner/Info.plist` for any hardware or sensitive APIs accessed by your app or third-party packages (see [Apple Info.plist Keys](https://developer.apple.com/documentation/bundleresources/information_property_list)):

```
<key>NSCameraUsageDescription</key>
<string>This app requires access to the camera to scan documents.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires access to your photo library to select a profile picture.</string>
```

App Store Connect also requires a [privacy manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files) (`PrivacyInfo.xcprivacy`) for apps that use required reason APIs, declaring why the app or its dependencies call them. Apple enforces this for every app submitted since May 1, 2024, whatever iOS version the app targets.

## Step 3: Create a Shorebird release

On a Mac with Xcode, build the release with Shorebird’s Flutter. This archives your app, exports a signed IPA, and uploads the compiled Dart code to Shorebird as the base that later patches apply to:

```
shorebird release ios
```

It writes the archive to `build/ios/archive/` and the IPA to `build/ios/ipa/`, and prints the ways to upload them when it finishes.

`shorebird release` takes the common `flutter build` options, such as `--flavor`, `--target`, `--build-name`, `--build-number` and `--dart-define`, and passes any other `flutter build` argument you put after a `--` separator. To sign with a specific certificate and provisioning profile, pass an `--export-options-plist`. See [Create a Release](/code-push/release/) for those, and run `shorebird release --help` for every option.

To obfuscate your Dart code, pass `--obfuscate`, and `--split-debug-info` with a directory to keep the symbol files in:

```
shorebird release ios --obfuscate --split-debug-info=build/symbols
```

Obfuscation on iOS needs a release built with Flutter 3.41.2 or later. Patches to an obfuscated release are obfuscated the same way automatically. See [Code obfuscation and debug symbols](/flutter-concepts/releasing-flutter-apps/#code-obfuscation-and-debug-symbols).

If you sign the app in a later step, pass `--no-codesign`. Shorebird then builds only the `.xcarchive` in `build/ios/archive/`. Sign and upload that archive, without rebuilding it, by hand in Xcode (the **Xcode** tab below) or in CI with `xcodebuild -exportArchive` or fastlane. Whatever signs and uploads it must keep the build number; see the caution in step 4.

## Step 4: Upload to App Store Connect

Upload the build from step 3 using one of these methods:

* Transporter app

  1. Download the [Transporter app](https://apps.apple.com/app/transporter/id1450874784) from the Mac App Store.
  2. Drag and drop the `.ipa` file from `build/ios/ipa/` into Transporter.
  3. Click **Deliver** to upload.

* Command line (xcrun)

  Upload with Apple’s `altool`, authenticating with an App Store Connect API key (see `man altool`):

  ```
  xcrun altool --upload-app --type ios \
    -f build/ios/ipa/*.ipa \
    --apiKey YOUR_API_KEY \
    --apiIssuer YOUR_ISSUER_ID
  ```

* Xcode

  1. Open the archive Shorebird built, which adds it to Xcode’s Organizer:

     ```
     open build/ios/archive/*.xcarchive
     ```

  2. Select it, click **Distribute App**, and choose **Custom**, then **App Store Connect** and **Upload**, so the dialog shows its distribution options.

  3. Uncheck **Manage Version and Build Number** (see the caution below), then follow the prompts to upload.

Keep the build number Shorebird recorded

If Xcode manages the version and build number while distributing, it rewrites the build number in the uploaded IPA, so it no longer matches the release Shorebird recorded, and patches won’t install. In Xcode’s Distribute App dialog, uncheck **Manage Version and Build Number**. In an `ExportOptions.plist`, set `manageAppVersionAndBuildNumber` to `false`: `shorebird release ios` refuses a plist that sets it to `true`, and if you export or upload the archive yourself with `xcodebuild`, Xcode turns it on by default when uploading. See [Your iOS build number is auto incrementing](/code-push/troubleshooting/#your-ios-build-number-is-auto-incrementing).

## Step 5: TestFlight and store submission

1. **TestFlight Internal Testing**: Available immediately after processing for members of your [App Store Connect](https://appstoreconnect.apple.com/) team.
2. **TestFlight External Testing**: Distribute builds to up to 10,000 external testers using [TestFlight](https://developer.apple.com/testflight/) (requires a brief initial Beta App Review from Apple).
3. **App Store submission**: Fill out App Store listing metadata, screenshots, age ratings, and privacy nutrition labels, then submit for [App Review](https://developer.apple.com/app-store/review/guidelines/). Apple offers **Phased Release for Automatic Updates** (rolling out over 7 days).

The build you test in TestFlight is the same build you submit, so it receives the same patches. To try a patch on TestFlight devices before everyone gets it, see [Testing patches](/code-push/guides/testing-patches/).

## Step 6: Ship fixes as patches

When you change Dart code in this version later, ship the change as a patch instead of a new store build:

```
shorebird patch ios --track=stable
```

A change to native code, a plugin with native code, assets such as images and fonts, or the Flutter version needs a new store release: bump the `version` in `pubspec.yaml`, then repeat steps 3 and 4. iOS patches only install on physical devices, not the Simulator. See [Create a Patch](/code-push/patch/).

## Building without Shorebird

For background: a Flutter app that doesn’t use Shorebird builds its store release with `flutter build ipa` (which writes the same `build/ios/archive/` and `build/ios/ipa/` outputs) or by archiving in Xcode. A build made with the standard Flutter has no Shorebird updater, so it can never receive a patch. Once your app uses Shorebird, use `shorebird release ios` for every build you upload to App Store Connect.
