ITMS-90683: Missing purpose string in Info.plist — what it means and the exact fix
ITMS-90683 means your binary references a protected API but Info.plist has no matching NS*UsageDescription key. Here is the exact fix for Expo and Xcode.
You uploaded a build from Xcode, Transporter or EAS Submit, and a few minutes later App Store Connect sent an email that starts with ITMS-90683: Missing purpose string in Info.plist. The upload is refused, the build never appears in TestFlight, and the message names a key such as NSCameraUsageDescription that it wants to see. Here is what Apple is actually checking and how to fix it without guessing.
What the error really says
The full message is worth reading once, because every sentence in it matters. Apple's wording is:
ITMS-90683: Missing purpose string in Info.plist - Your app's code references
one or more APIs that access sensitive user data, or the app has one or more
entitlements that permit such access. The Info.plist file for the "YourApp.app"
bundle should contain a NSCameraUsageDescription key with a user-facing purpose
string explaining clearly and completely why your app needs the data. If you're
using external libraries or SDKs, they may reference APIs that require a purpose
string. While your app might not use these APIs, a purpose string is still
required.Three things follow from that text. First, the check runs against the compiled binary, not your source: if any code in the app links a symbol that can touch a protected resource (camera, photo library, location, microphone, contacts, calendar, Bluetooth, Face ID and others), the corresponding usage-description key must exist. Second, it does not matter whether your own code ever calls that API — a third-party SDK that merely references AVCaptureDevice is enough. Third, the key has to be in the Info.plist that ships inside the .ipa, with a non-empty value.
Why it fires even though “the app does not use the camera”
This is the part that confuses first-time submitters. A typical chain looks like this: you add an image-picker or QR-scanner package for one screen, or an analytics / ads SDK that has optional camera or location features. The package links the framework; the framework's symbols end up in your executable; Apple's upload scanner finds the symbol and asks for the purpose string. Common triggers by key:
NSCameraUsageDescription— image pickers, QR/barcode scanners, video-call and AR SDKs.NSPhotoLibraryUsageDescription— anything that saves or picks images (UIImagePickerController,PHPickerViewController, share sheets in some SDKs).NSLocationWhenInUseUsageDescription— maps, weather, ads and attribution SDKs that read coarse location.NSMicrophoneUsageDescription— audio recording, voice notes, video capture.NSContactsUsageDescription,NSCalendarsUsageDescription,NSBluetoothAlwaysUsageDescription,NSFaceIDUsageDescription— contact pickers, calendar sync, BLE devices, biometric login.
Apple's own documentation on requesting access to protected resources is explicit that a missing purpose string is not just a review problem: if the app actually reaches the API at runtime without the key, the access attempt fails and the app may crash. So the upload check is doing you a favour.
The fix in Xcode (native / Swift)
Open the target's Info tab (or Info.plist directly), add the key named in the email, and write one complete sentence that a user would understand before tapping Allow. In XML form:
<key>NSCameraUsageDescription</key>
<string>The camera is used to scan the QR code on your ticket.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photos are used to attach a picture to your support request.</string>Newer Xcode projects often keep these values as build settings instead of plist entries. The setting is INFOPLIST_KEY_NSCameraUsageDescription (same pattern for every key) under the target's Build Settings; Xcode writes it into the generated Info.plist at build time. Either place works — what matters is the final plist inside the archive.
The fix in Expo (app.json / app.config.js)
With Expo or any tool that builds on Expo (Rork, a0, Bolt and similar), you never edit Info.plist by hand. Put the keys under expo.ios.infoPlist and rebuild with EAS:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "The camera is used to scan the QR code on your ticket.",
"NSPhotoLibraryUsageDescription": "Photos are used to attach a picture to your support request."
}
}
}
}Many Expo modules (expo-camera, expo-image-picker, expo-location) also accept the text through their config plugin options, which writes the same key. Using both is harmless; using neither is what produces the email. Developers on Apple's forums also report the email when the value lives only in a localized InfoPlist.strings file, so keep the base key in Info.plist itself.
Write a real sentence, not a placeholder
Passing the upload check is only half of it. App Review Guideline 5.1.1(ii) says purpose strings must “clearly and completely describe your use of the data”. A value like “This app needs camera access” or “Required” clears ITMS-90683 and then gets rejected by a human reviewer a day later. State the feature: what the user gets in exchange for the permission.
Do not add every key “just in case”
The tempting shortcut is to paste every usage-description key you can find into the plist and move on. That trades one problem for another: reviewers do ask why an app declares a permission it never uses, under the data-minimization language in the same guideline, and templates from no-code tools frequently ship with camera, photo and location keys pre-filled. Declare exactly the permissions your binary can reach and nothing else.
Still getting the email after adding the key?
- Stale build. The key was added after the archive was made. Re-archive (or run
eas buildagain) and upload the new .ipa; do not re-upload the old file. - Empty value. An empty string counts as missing. The value must contain text.
- Wrong target. In multi-target projects (app + extension + watch app) the key must be in the Info.plist of the bundle named in the email, which is not always the main app.
- Wrong key.
NSLocationAlwaysAndWhenInUseUsageDescriptionandNSLocationWhenInUseUsageDescriptionare different keys; copy the one the email names exactly.
If you would rather see the whole list before uploading — which symbols the binary references and which usage descriptions the shipped Info.plist actually contains, including keys declared but never used — RejectProof's binary scan does exactly that comparison inside your browser, without uploading the .ipa anywhere.