When flutter build ipa --flavor staging fails with 'You must specify a --flavor option to select one of the available schemes', how must the Xcode project be set up?
answer
- scheme name matches the flavor
- Debug-, Profile-, Release-staging
- missing config falls back to Release
- per-configuration bundle ID and name
basics
~20 sThe Xcode project needs a shared scheme whose name matches the flavor, plus build configurations named Debug-staging, Profile-staging and Release-staging assigned to that scheme's actions; per-configuration settings then give the flavor its own bundle ID and name.
solid answer
~40 sFor iOS the Flutter tool maps `--flavor staging` to an Xcode **scheme**: it looks for a scheme matching the flavor name, ignoring case, and exits listing the available schemes when none matches — the error in the question. The scheme must be **shared** so the tool and CI can see it. The tool then expects a **build configuration** named after the mode and scheme — `Debug-staging`, `Profile-staging`, `Release-staging` — made by duplicating `Debug`, `Profile` and `Release`, and assigned to the scheme's Run, Test, Profile, Analyze and Archive actions. If no configuration matches, the tool quietly falls back to plain `Release`, so a "staging" build can ship production's settings. `PRODUCT_BUNDLE_IDENTIFIER` and the display name are then set per configuration, for every flavor you pass.
code
bash · 5 lines# Lists schemes and build configurations the tool will see
xcodebuild -list -project ios/Runner.xcodeproj
# Builds the staging flavor with its Release-staging configuration
flutter build ipa --flavor staging --dart-define-from-file=config/staging.jsongo deeper
Know that an iOS flavor is an Xcode scheme with the same name, and that each scheme needs Debug-, Profile- and Release-suffixed configurations.
Explain how the tool matches the scheme and configuration names, why schemes must be shared, and which build settings differ per configuration.
Diagnose flavored iOS failures from the tool's messages, keep flavor names consistent across platforms, and make CI builds reproducible from a clean checkout.
Consider the cost each extra flavor adds to Xcode configurations, signing and CI, and whether fewer native flavors with more define files would do.
## How the Flutter tool finds an iOS flavor On Android a Flutter flavor is a Gradle product flavor. On **iOS and macOS** there is no product-flavor concept, so the Flutter tool maps a flavor onto two Xcode concepts: - a **scheme**, which says what to build and with which configuration for each action (Run, Test, Profile, Analyze, Archive); - a **build configuration**, which holds build settings such as the bundle identifier. When you run `flutter build ipa --flavor staging`, the tool: 1. reads the schemes and configurations from the Xcode project; 2. looks for a scheme named after the flavor — it first tries the sentence-cased name (`Staging`) and otherwise accepts a unique case-insensitive match (`staging`); 3. looks for a build configuration named `<Mode>-<scheme>` — for a release build, `Release-staging` — then for a **unique** name containing both the mode and the scheme (so `Staging Release` would also be accepted), and finally **falls back to the plain `Release` configuration**. If step 2 fails and the project defines custom schemes, the tool prints `The Xcode project defines schemes: ...` and exits with `You must specify a --flavor option to select one of the available schemes.` If the project defines no custom schemes at all, it exits saying `--flavor` cannot be used. Step 3 errors only when several configurations match ambiguously, or when even the base configuration is missing; then it prints that it expects a configuration named `Release-staging` "or similar". The silent fallback to `Release` is the dangerous case: the build succeeds, but with the base configuration's bundle identifier and settings. ## Setting it up The Flutter documentation's iOS flavor guide describes this layout: 1. **Create schemes** named `staging` and `production` (Product > Scheme > New Scheme, target `Runner`). 2. **Share them** — in Manage Schemes, tick *Shared* so the scheme files live in the project directory and are committed. A scheme that exists only in one developer's user data is invisible to a CI checkout. 3. **Duplicate configurations** in the project's Info tab: from `Debug`, `Profile` and `Release` create `Debug-staging`, `Profile-staging`, `Release-staging`, and the same for `production`. 4. **Assign configurations to scheme actions** — for `staging`: Run, Test and Analyze use `Debug-staging`; Profile uses `Profile-staging`; Archive uses `Release-staging`. 5. **Set per-configuration build settings** — `PRODUCT_BUNDLE_IDENTIFIER` such as `com.example.telemed.staging`, a display name, and an app icon set per flavor. | Scheme | Run / Test / Analyze | Profile | Archive | |---|---|---|---| | `staging` | `Debug-staging` | `Profile-staging` | `Release-staging` | | `production` | `Debug-production` | `Profile-production` | `Release-production` | ## Why duplicate rather than create empty configurations Flutter's template `ios/Flutter/Debug.xcconfig` and `Release.xcconfig` begin with `#include "Generated.xcconfig"`, the file the tool rewrites on every build with the Flutter root, build name and number, encoded defines and similar settings. The Flutter guide says the new configurations should be **based on `Debug.xcconfig` and `Release.xcconfig`** (Profile uses `Release.xcconfig` by default), not on `Pods-Runner.xcconfig`, and that the appended flavor name should be lowercase so the CLI recognises it. Duplicating an existing configuration carries that base configuration file over; a hand-made empty configuration does not, and the build then lacks settings the tool relies on. ## Diagnosing failures - **The error in the question** — no scheme matches `staging`: the scheme is missing, misnamed, or not shared on the CI machine. - **"does not define custom schemes"** — only the default `Runner` scheme exists; flavors were set up for Android only. - **Expects a build configuration named `Release-staging` or similar** — several configurations match ambiguously (for example `Release-staging` and `Release-staging-old`). - **The staging build installs over production** — either both configurations share one `PRODUCT_BUNDLE_IDENTIFIER`, or `Release-staging` does not exist and the tool silently used plain `Release`. - **Signing fails only for staging** — the new bundle identifier needs its own provisioning profile; that belongs to the signing setup rather than the flavor. ## Other per-flavor settings on iOS Once configurations exist, anything that is a build setting can differ per flavor: - **Display name** through a user-defined setting such as `APP_DISPLAY_NAME`, referenced as `$(APP_DISPLAY_NAME)` from `CFBundleDisplayName` in `Info.plist`, as the Flutter guide shows. - **App icon** through the per-configuration App Icon setting (App Icons and Launch Screen), pointing each configuration at its own icon set such as `AppIcon-staging`. - **Bundle identifier** through `PRODUCT_BUNDLE_IDENTIFIER`, which is what lets staging and production install side by side. - **Flavor-specific `.xcconfig` files**, which the Flutter guide suggests for settings such as a per-configuration `API_BASE_URL` read by native code. Values that Dart needs belong in define files instead; build settings are for the native shell. ## Keep platforms in step Flavor names must be identical across Android `productFlavors`, Xcode schemes and anything else that switches on them. Because `appFlavor` holds the string exactly as passed on the command line while the scheme lookup ignores case, pick lowercase names everywhere and use them consistently in scripts.
- Why does the flavored build work on a developer's Mac but fail on CI with the scheme error?The scheme was created unshared, so it lives in that developer's user data inside the project bundle and is not committed. Marking it Shared in Manage Schemes stores it under `xcshareddata`, which a fresh CI checkout can see.
- What breaks if you create Release-staging as a new empty configuration instead of duplicating Release?It has no base configuration file, so it misses `Release.xcconfig` and, through it, the `Generated.xcconfig` settings the Flutter tool writes. Build settings the tool relies on are then absent. Duplicating `Release` keeps `Release.xcconfig` as the base, which is what the Flutter guide asks for.
saying these in an interview costs you the question
- Leaves the new Xcode scheme unshared and expects CI to find it
- Assumes a missing Release-staging configuration always fails the build loudly
- Thinks an Android product flavor is enough for iOS flavored builds
- Creates empty build configurations instead of duplicating Debug, Profile and Release
- Gives staging and production the same PRODUCT_BUNDLE_IDENTIFIER