A Flutter church-events app's App Store build is rejected for a missing purpose string; what is a purpose string, and where do you add it?
answer
- iOS asks why, in your words
- NS...UsageDescription keys
- ios/Runner/Info.plist, not pubspec.yaml
- plugins document keys but never add them
- build ipa validation does not check
basics
~20 sA purpose string is an NS...UsageDescription key, such as NSCameraUsageDescription, in ios/Runner/Info.plist whose text iOS shows in the permission prompt; each protected resource the app uses needs one, and Flutter plugins never add it for you.
solid answer
~40 sOn iOS every protected resource, such as the camera, microphone, photo library or location, requires an `Info.plist` key named `NS…UsageDescription` whose string value explains, in the user's language, why the app needs it; iOS shows that text in the permission prompt. For the church-events app, adding a camera plugin to scan event check-in codes means adding `NSCameraUsageDescription` (and `NSMicrophoneUsageDescription` if it records video) to `ios/Runner/Info.plist`. Flutter plugins list the keys they need in their docs but cannot inject them into the host app, and `flutter build ipa`'s validation summary does not check them. A missing key is flagged when App Store Connect processes the upload, and at runtime iOS terminates an app that touches the resource without one.
code
xml · 5 lines<!-- ios/Runner/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Scan the check-in code at the church door.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Attach photos from your library to an event recap.</string>go deeper
Recall that each protected resource needs an NS...UsageDescription key in ios/Runner/Info.plist, and that you add it yourself when adding a plugin.
Explain why plugins cannot add the key, why testing can hide the gap, and what iOS does at runtime without it.
Build a release checklist: map each plugin to its keys, catch linked-but-unused APIs, translate the strings, and review the text as part of release.
Treat permission text as product copy with an owner, and decide how new native dependencies get a privacy review before they reach a release branch.
## What a purpose string is iOS gates access to **protected resources** behind a system permission prompt. The prompt's title is written by iOS; the explanatory sentence under it is yours, taken from a key in the app's `Info.plist`. These keys all follow the pattern `NS<Resource>UsageDescription` and are called **purpose strings** or usage descriptions. In a Flutter project the file is **`ios/Runner/Info.plist`**, the Runner target's property list. Nothing in `pubspec.yaml` feeds it purpose strings. ## Scenario: the church-events app The app lists services and events, lets volunteers scan a check-in code at the door, and lets members attach a photo to an event recap. The team adds a camera plugin, tests on a simulator and on their own phones where permission was granted long ago, and uploads. App Store Connect's processing flags the build because the binary links camera APIs while `Info.plist` has no `NSCameraUsageDescription`. The fix is one key per resource, with honest, specific text: | Resource used | Key | Example text | |---|---|---| | Camera for check-in scanning | `NSCameraUsageDescription` | Scan the check-in code at the church door. | | Microphone for video recaps | `NSMicrophoneUsageDescription` | Record audio for event recap videos. | | Photo library for recap photos | `NSPhotoLibraryUsageDescription` | Attach photos from your library to an event recap. | | Location while the app is open | `NSLocationWhenInUseUsageDescription` | Show events near you. | ## Why the build was flagged even though it ran fine - **Plugins do not merge plist keys.** A plugin can ship native code, a podspec or a Swift package, but it cannot add keys to the host app's `Info.plist`. The Flutter camera cookbook tells you to add `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` yourself. - **The check is on what is linked.** Processing looks at the APIs the binary references, so a plugin that links an API the app never calls can still require a key; such plugins usually document a build switch to compile the unused API out. - **`flutter build ipa` does not check it.** Its App Settings Validation reports version, build number, display name, deployment target and bundle ID, and flags placeholder icons; purpose strings are outside its scope. - **Testing hides it.** A device that already granted access never shows the prompt again. ## What happens at runtime without the key If the code path actually runs, iOS does not fall back to a generic prompt: it **terminates the app** at the moment the protected resource is accessed. That is why a missing key is a release blocker, not a cosmetic warning. Requesting the permission, handling a denial and sending the user to Settings are separate concerns handled at runtime. ## Writing the text well 1. Say what the feature does for the user, not that the app "needs access". 2. Name the specific use: scanning a door code is clearer than "camera features". 3. Translate it: per-locale `InfoPlist.strings` files in Xcode override the `Info.plist` value, which stays the fallback. 4. Keep it true: text promising one use while the app does another invites a review problem. ## Finding every key before upload A short routine catches the gap before App Store Connect does: - For each plugin in `pubspec.yaml` with iOS code, read its README's iOS setup section and copy the keys it lists. - Search `ios/Runner/Info.plist` for every `UsageDescription` and check that each has a real sentence, not an empty string or a placeholder. - Install the release build on a device that has never run the app and walk through each feature that touches a protected resource; a missing key terminates the app on the spot. - Keep the keys under review whenever a dependency is upgraded, because a new plugin version may link an API the old one did not. ## Related keys that are not purpose strings - The **privacy manifest**, `PrivacyInfo.xcprivacy`, declares collected data and required-reason APIs. It is a separate file and does not replace purpose strings. - Version keys such as `CFBundleShortVersionString` and `CFBundleVersion` live in the same `Info.plist` but are filled from `pubspec.yaml`'s `version`.
- Why did the missing key never show up while testing on the team's phones?Those devices had granted access earlier or never reached the code path, so iOS never needed to show the prompt. The key is still required: upload processing flags it, and a fresh install that reaches the camera terminates.
- How do you translate a purpose string for a Flutter iOS app?Add per-locale `InfoPlist.strings` files to the Runner target in Xcode, keyed by the same `NS…UsageDescription` names. The value in `Info.plist` remains the base-language fallback. Flutter's `gen-l10n` ARB files do not reach `Info.plist`.
saying these in an interview costs you the question
- Adding the plugin to pubspec.yaml adds its Info.plist keys automatically.
- Purpose strings only matter at runtime; the upload never checks them.
- Without the key, iOS shows the prompt with a generic system message.
- flutter build ipa refuses to archive when a purpose string is missing.
- Purpose strings moved into PrivacyInfo.xcprivacy and left Info.plist.
- Any placeholder such as 'needs camera' is fine as purpose text.