You uploaded the build, processing finished, and instead of Waiting for Review you got an email from App Store Connect with error ITMS-90683: a missing Purpose String for the camera or photo library. For a developer shipping from Xcode, Expo, or FlutterFlow, the confusing part is that the rejection often names a permission you do not remember adding.
Short answer
ITMS-90683 means your build can reach the camera or photo library, but Info.plist has no usage-description key explaining why. Add NSCameraUsageDescription for the camera and NSPhotoLibraryUsageDescription for the photo library, each with a specific reason the user reads in the permission prompt. Apple has required a purpose string for every app that accesses protected data since spring 2019. Because the keys live in Info.plist, the fix ships in a new build, not a metadata edit.
What you should know
- ITMS-90683 is a binary check, not a content review: it fires at upload or during processing, before a human opens the app.
- Each protected resource has its own key: camera, photo library, microphone, and location use different Info.plist keys.
- A bundled SDK can trigger it: a library that references a camera or photo API requires the key even when your own code never calls it.
- The string has to be specific: a vague or empty reason can be rejected separately under Guideline 5.1.1.
- The fix needs a new build: Info.plist is compiled into the binary, so editing App Store metadata cannot resolve it.
Which Info.plist key does each permission need?
The short answer is that the key name has to match the exact resource the build can touch. App Review reads Info.plist for a usage-description string keyed to each protected API your binary references. The table maps the common camera and photo keys.
| Resource | Info.plist key | When it is required |
|---|---|---|
| Camera | NSCameraUsageDescription | The build can capture photo or video |
| Photo library (read or write) | NSPhotoLibraryUsageDescription | The build can read from or save to the library |
| Photo library (add only) | NSPhotoLibraryAddUsageDescription | The build only saves images and never reads them |
| Microphone | NSMicrophoneUsageDescription | The build can record audio, including video with sound |
The add-only key is the one most developers miss. An app that only saves a generated image to the camera roll still needs NSPhotoLibraryAddUsageDescription, and omitting it produces the same ITMS-90683 rejection as a missing camera key.
Why is my app flagged for a permission it never uses?
Because the requirement is triggered by the API the binary can reach, not by the feature you built. A third-party SDK compiled into your app can reference a camera or photo library API, and Apple's static check sees that reference at the binary level. FlutterFlow's guidance on this rejection states plainly that an external library may reference these APIs even when your app does not, and that a purpose string is still required. The practical consequence is that an analytics, chat, or image-picker SDK can force you to declare a key for a feature your users never see. The limit here is that you often cannot tell from your own source which dependency pulled the API in, which is why scanning the compiled build is more reliable than rereading your code.
How do you add the Purpose String in a native, Expo, or FlutterFlow project?
The idea is the same in every stack: put the key and a human-readable reason into the final Info.plist, then rebuild. The place you edit differs.
In a native Xcode project, add the keys to Info.plist directly:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera so you can take and attach photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app accesses your photos so you can upload them to your profile.</string>
In an Expo project, declare them in app.json under ios.infoPlist, and Expo writes them into Info.plist during prebuild:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "This app uses the camera so you can take and attach photos.",
"NSPhotoLibraryUsageDescription": "This app accesses your photos so you can upload them."
}
}
}
}
In FlutterFlow, open the project Permissions settings, enable the camera and photos permissions, and write a clear description for each. FlutterFlow injects the matching keys into Info.plist when it builds the iOS bundle, so the edit happens in the builder rather than in a file you open by hand.
Does fixing a Purpose String need a new build or just a metadata reply?
It needs a new build. Info.plist is compiled into the app bundle, so the only way to change a usage-description string that App Store Connect validates is to rebuild and re-upload. That returns the submission to Waiting for Review for a full review, the same reset behavior any new binary triggers. A metadata reply cannot clear ITMS-90683, because the missing data sits inside the binary and not in the App Store listing. The useful habit is to batch this fix with any other binary-level changes, rather than uploading once per key and restarting review each time.
What to watch out for
Two traps show up after the first fix. The first is the vague string. A key that only says "This app needs camera access" can clear ITMS-90683 and then draw a separate rejection under App Store Review Guideline 5.1.1, which asks for a specific, user-facing reason; name the feature the user gets when they allow it. The second is the assumption that you should declare a key only for features you wrote. The check is driven by what the binary can reach, including SDK code, so map every protected API your build actually links, then declare exactly those. Mapping them by hand is error-prone, which is the argument for an external read of the build. For builders who want a list of every permission an APK, AAB, or IPA actually requests before submitting, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS.
What to take away
- ITMS-90683 means a usage-description key is missing for a camera or photo API your binary can reach; add the matching NS...UsageDescription key and rebuild.
- The trigger is the API the build links, not the feature you wrote, so a bundled SDK can require a key for a permission your users never see.
- Write a specific reason in each string, because a vague one can pass the binary check and still be rejected under Guideline 5.1.1.
- The keys live in the binary, so the fix always rides a new build; a pre-submission scan such as PTKD.com that lists every requested permission helps you declare exactly the right keys the first time.




