ITMS-90683: Missing purpose string in Info.plist
You uploaded a build, and minutes later Apple emailed you this:
ITMS-90683: Missing purpose string in Info.plist — Your app's code
references one or more APIs that access sensitive user data. The app's
Info.plist file should contain a NSCameraUsageDescription key with a
user-facing purpose string explaining clearly and completely why your
app needs the data.The build is rejected before it ever reaches a human reviewer. Here is what causes it and how to fix it properly.
What the error actually means
iOS gates a set of APIs behind user consent — camera, microphone, photos, location, contacts, calendar, motion, tracking. Before the system shows the permission prompt, it needs a sentence to display: why does this app want your camera?
That sentence lives in your Info.plist, under a key specific to each permission. If your binary references a gated API and the matching key is absent, Apple's static analyzer catches it at upload time and returns ITMS-90683.
Two things follow from this being a static check:
- It fires on the API being referenced, not called. Dead code, a commented-out feature you left in, or a helper you never wired up will still trigger it.
- It fires on code inside your dependencies too. A SDK that touches
PHPhotoLibrarymakes the key your responsibility, not theirs.
If you ship without the key and Apple's analyzer somehow misses it, the app doesn't merely get rejected — it crashes the moment the API is called. The purpose string is not paperwork; it's a runtime requirement.
The key for each API
| API you reference | Info.plist key required |
|---|---|
AVCaptureDevice, AVCaptureSession | NSCameraUsageDescription |
AVAudioRecorder, requestRecordPermission, AVAudioApplication | NSMicrophoneUsageDescription |
PHPhotoLibrary, PHPickerViewController, UIImagePickerController | NSPhotoLibraryUsageDescription |
CLLocationManager | NSLocationWhenInUseUsageDescription |
CNContactStore | NSContactsUsageDescription |
ATTrackingManager | NSUserTrackingUsageDescription |
EKEventStore | NSCalendarsUsageDescription |
CMMotionManager | NSMotionUsageDescription |
Fixing it
Add the key to your target's Info.plist:
xml
<key>NSCameraUsageDescription</key>
<string>Scan a document to attach it to a report.</string>Then bump the build number and re-upload. There is no way to fix this server-side — the check runs against the binary, so it needs a new binary.
Write a string that survives review
Apple rejects purpose strings that don't explain anything. These get sent back under guideline 5.1.1:
| Rejected | Why |
|---|---|
Camera access | Restates the permission, explains no purpose. |
This app needs the camera. | Same. "Needs" is not a reason. |
Required for full functionality | Vague, and reviewers read it as evasive. |
What passes is a concrete user-facing benefit, in the user's language:
| Accepted |
|---|
Scan a receipt to add it to an expense. |
Record a voice note to attach to a task. |
Show nearby stations on the map. |
Two practical notes:
- The string is localized. Put it in
InfoPlist.stringsfor each language you ship, not just the base one. A French user seeing an English purpose string is a poor first impression, and reviewers testing in another locale have flagged it. - Keep it to one sentence. The system prompt truncates long strings.
Finding what triggered it
Apple's email names the key, not the code. If you don't know why your app references the API, search the whole project — including packages:
sh
grep -rn "AVCaptureDevice\|AVCaptureSession" --include="*.swift" .If nothing turns up in your own sources, it came from a dependency. Check your Swift packages and any embedded frameworks. You still have to declare the key.
Catching it before you upload
The upload-reject-fix-reupload loop costs 20 minutes each time — archive, upload, wait for processing, read the email, fix, repeat. The check itself is mechanical: cross-reference the privacy APIs in your sources against the keys in your Info.plist.
That's what AuditStore does locally. Its usage-strings check parses your sources and your Info.plist and reports any gated API missing its key as Blocking, before you archive. No upload, no waiting.
Related
- Xcode Project checks — the full local static analysis
- App Information — App Privacy declarations, which are separate from purpose strings
- Export compliance and Missing Compliance