Privacy

    What does ITMS-90683 Missing NSPhotoLibraryUsageDescription mean?

    Xcode project Info tab open on an iOS target, showing the Privacy Photo Library Usage Description key being added with a one-sentence purpose string before resubmitting to App Store Connect to clear an ITMS-90683 rejection

    App Store Connect just sent the rejection email with ITMS-90683 in the subject line, and the offending key is NSPhotoLibraryUsageDescription. The build compiled cleanly, the simulator never asked for photo permission, and the upload finished without complaint. Now the binary sits in the Invalid Binary state and the release is on hold.

    Short answer

    ITMS-90683 with NSPhotoLibraryUsageDescription is a static-analysis rejection from App Store Connect. The validator scans your IPA, finds at least one symbol that links against the Photo Library APIs, and cannot find a matching purpose string in Info.plist. The fix is one key with one honest sentence, plus a new build number before resubmission. The offending symbol often comes from a third-party SDK rather than your own code.

    What you should know

    • The validator inspects symbols in the compiled binary, not runtime behavior. Any linked framework that references PhotoKit, AssetsLibrary, PHPickerViewController, or UIImagePickerController is enough to trigger the rejection, even when the user-facing code never opens the photo picker.
    • Apple has required purpose strings for the photo library since iOS 10.0. Apps that try to read or write the camera roll without NSPhotoLibraryUsageDescription crash on first access, and uploads to App Store Connect fail validation before App Review ever sees the binary.
    • Empty strings do not pass the validator or the runtime. The key has to carry a non-empty human-readable value. iOS treats an empty string as undefined behavior at the permission prompt.
    • Two related keys cover different access patterns. NSPhotoLibraryUsageDescription is the read-and-write key, available since iOS 6.0. NSPhotoLibraryAddUsageDescription, introduced in iOS 11.0, is write-only and shows a softer prompt.
    • Editing Info.plist by hand frequently makes the rejection worse. A misplaced bracket or duplicate entry causes the validator to treat the key as absent, which loops developers back to the same email.
    • Resubmission requires a new build number. App Store Connect refuses to accept the same CFBundleVersion twice within a release, regardless of which key you added.

    Why does App Store Connect block a build with ITMS-90683?

    The short answer is that ITMS-90683 is a static check, not a behavioral one. When you upload an IPA through Xcode, Transporter, or altool, App Store Connect runs a server-side validator that inspects every symbol in the compiled binary and every framework you ship. If any of those symbols match the list of APIs Apple has flagged as sensitive for the photo library, the validator looks for the matching purpose string key in Info.plist. When it is missing, the build moves to the Invalid Binary state and you receive the ITMS-90683 email with NSPhotoLibraryUsageDescription named in the body.

    The enforcement is grounded in Apple's privacy guidance for iOS apps, which states that an iOS app linked on or after iOS 10.0 must statically declare its intent to access the photo library. The check runs before the binary reaches a human reviewer, which is why some teams see the rejection within minutes of the upload while the rest of the submission is still processing. Static analysis means the validator does not care whether the code path ever runs at runtime. The symbol on disk is enough.

    Which APIs and frameworks trigger the requirement?

    Four common API surfaces pull the NSPhotoLibraryUsageDescription requirement into a build. PhotoKit, the modern interface that lives in the Photos and PhotosUI frameworks, is the largest source. UIImagePickerController in UIKit triggers the same gate, because the picker reads from the camera roll. PHPickerViewController, introduced in iOS 14 as the limited-access alternative, also touches the photo library symbol space. The older AssetsLibrary framework, deprecated since iOS 9, still triggers a match when a legacy SDK ships with it.

    Vendor SDKs are the usual cause. Analytics frameworks that capture user feedback with attached screenshots, share extensions that present a system share sheet, social SDKs that paste a card to a story, customer-support widgets with photo upload, and AR SDKs that load reference images all link Photo Library symbols. A GitHub issue on the FlutterFlow tracker documents this pattern on builder platforms, where the developer never wrote a line of Swift but the bundled plugins added PhotoKit references during the iOS build.

    To find the source in your own binary, run nm against the executable inside the .app folder and grep for Photo, PHAsset, ALAsset, or UIImagePicker. The output names the framework and the specific symbol. That tells you whether the reference is yours or comes from a SwiftPM dependency, a CocoaPod, or a vendored framework.

    How do you add NSPhotoLibraryUsageDescription correctly in Xcode?

    The cleanest path uses the Info tab of the project editor rather than the raw plist file. Open the project in Xcode, select the target, open the Info tab, click the plus icon under Custom iOS Target Properties, and start typing the key name. Xcode autocompletes to "Privacy - Photo Library Usage Description" and writes the underlying XML correctly. Set the value to one sentence describing why your app reads photos, save, and rebuild.

    Developers on the Apple Developer Forums repeatedly note that direct edits to Info.plist in a text editor cause silent corruption: the indentation looks fine to a human, but the binary plist encoder produces a structure the validator treats as missing. If you must edit the XML by hand, run plutil -lint ios/App/Info.plist after every change to confirm the file parses cleanly. Then increment CFBundleVersion in the same build before re-archiving, otherwise App Store Connect rejects the upload with a separate error about the duplicate build number.

    For builders who want an external automated pass over the build before resubmission, PTKD.com (https://ptkd.com) is one of the platforms focused on scanning compiled IPA and AAB files for missing privacy declarations and other OWASP MASVS findings. The goal is to catch the gap on a local machine, not on Apple's servers, where each rejection costs another review cycle.

    What is the difference between NSPhotoLibraryUsageDescription and NSPhotoLibraryAddUsageDescription?

    The two keys cover different access levels and were introduced in different iOS versions. NSPhotoLibraryUsageDescription is the broader read-and-write key. NSPhotoLibraryAddUsageDescription is the narrower write-only key. If your app only saves an image to the camera roll, the Add variant is enough. If your app reads any existing photo, opens an image picker, or scans for assets, the Usage variant is required. Apps that both read and write declare both keys.

    KeyiOS minimumAccess scopeWhen to use
    NSPhotoLibraryUsageDescriptioniOS 6.0Read and writeApp opens the image picker, reads existing photos, or scans for assets
    NSPhotoLibraryAddUsageDescriptioniOS 11.0Write onlyApp exports a generated image, saves a video, or adds a downloaded photo
    Both keys presentiOS 11.0Read and write, softer write promptApp reads existing photos and also saves generated content
    Neither key with Photo Library symbolsn/aRuntime crash; binary rejected with ITMS-90683Never the correct state

    The system displays the broader prompt first when the app calls the read path. The Add-only prompt presents a smaller dialog and skips the Limited Access picker that appears for full library access. Picking the right key reduces friction in the permission flow and lets the user understand the actual scope of the request.

    How do cross-platform projects expose the photo library key?

    Every framework that produces an iOS binary goes through the same App Store Connect validator, so every framework needs the same key. The configuration surface differs by tool.

    • Expo writes Info.plist from the ios.infoPlist section of app.json or app.config.js. Per Expo's configuration documentation, add "NSPhotoLibraryUsageDescription": "..." inside that object and run npx expo prebuild --clean to regenerate the native project.
    • React Native keeps Info.plist at ios/[ProjectName]/Info.plist. Add the key through Xcode rather than a text editor.
    • Flutter uses ios/Runner/Info.plist. Edit it through Xcode by opening ios/Runner.xcworkspace and selecting the Runner target's Info tab.
    • FlutterFlow exposes a Custom Info.plist Entries section under App Settings, Permissions. Add the key name and the value there; the next iOS build includes it.
    • Capacitor edits ios/App/App/Info.plist directly through Xcode.

    The destination file is the same Info.plist in every case. The validator does not care which tool wrote the entry as long as the XML is well-formed.

    What kind of purpose string does Apple actually accept?

    The automated ITMS-90683 check passes any non-empty string, but a human reviewer in App Review can still flag a vague description under Guideline 5.1.1 of the App Store Review Guidelines. Strings like "Used for photos", "App needs access", or a single brand name regularly draw a manual rejection during review.

    A purpose string that clears both gates names the feature and the user benefit in one sentence. Examples that pass:

    • "Lets you attach a photo from your camera roll to a chat message."
    • "Used to set a profile picture from photos already on your device."
    • "Saves the receipts you generate inside the app to your camera roll."

    Examples that risk a manual review rejection:

    • "Photos."
    • "App requires this permission."
    • "Will use to make app better."

    The same string is displayed inside the system permission alert at runtime, so a clear sentence does double duty: it clears the App Review gate and earns more Allow taps from real users.

    What to watch out for

    Several traps surface during the fix and the resubmission. The first is forgetting to increment CFBundleVersion: the validator rejects the upload before it ever checks the new key, and the developer concludes that the purpose string did not take. Increment the build before every archive.

    The second is adding NSPhotoLibraryUsageDescription only to the main target and not to extensions such as a Share Extension or a Notification Service Extension when those extensions also link Photo Library frameworks. Each extension carries its own Info.plist.

    The third is treating an SDK update as the cause of the rejection. Many teams add the key, ship one release, then remove the key when they audit dependencies, only to see the rejection return on a later build when a different SDK update reintroduces the symbol. The safer pattern is to keep the key in place once it has been added, because the runtime alert only fires when an API path actually requests access.

    The fourth is removing the key as a privacy-conscious move without removing the SDK that pulls it in. The result is a runtime crash the first time any code path touches Photo Library APIs, often on a user device rather than in your testing.

    Key takeaways

    • Treat ITMS-90683 as a static-analysis result, not a behavioral one. The validator scans symbols in the compiled binary, so the fix lives at the Info.plist layer regardless of whether your own code uses the photo library.
    • Pick the narrow key when you can. NSPhotoLibraryAddUsageDescription is enough for apps that only export images, and it shows a less alarming prompt than the full read-and-write key.
    • Write a specific purpose string the first time. Vague descriptions clear the automated validator but get flagged in human review, which costs another full review cycle and another build number.
    • Increment the build number before every resubmission. Otherwise the new key never gets the chance to clear the gate.
    • Some teams outsource pre-submission validation to platforms like PTKD.com (https://ptkd.com), which scans compiled IPA and AAB files for missing privacy declarations and other OWASP MASVS findings before the build is uploaded to App Store Connect.
    • #itms-90683
    • #nsphotolibraryusagedescription
    • #info.plist
    • #app store connect
    • #ios
    • #privacy
    • #purpose string

    Frequently asked questions

    How do I find which SDK is referencing the Photo Library in my binary?
    Use nm or otool on the compiled binary inside the .app folder. Run `nm /path/to/App.app/App | grep -i photo` to list every Photo Library symbol the linker pulled in, then run `otool -L App.app/App` to list dynamic dependencies. Symbols such as PHAsset, PHPhotoLibrary, ALAssetsLibrary, or UIImagePicker indicate Photo Library usage. The output tells you whether the reference comes from your own code, a Swift Package, a CocoaPod, or a vendored XCFramework.
    Will Apple reject the build a second time if my purpose string is short or generic?
    The automated ITMS-90683 check passes any non-empty string, but a human App Reviewer can still flag a vague description under Guideline 5.1.1. Patterns reported on the Apple Developer Forums show that one-word values, brand names, or generic phrases like 'App needs this permission' often draw a manual rejection. A specific sentence such as 'Lets you attach a photo from your camera roll to a chat message' clears both gates in one resubmission.
    Does deleting the SDK that imports PhotoKit avoid the need for a purpose string?
    Sometimes, but rarely. If the dependency is the only reason the symbol is in the binary, removing it and clearing DerivedData lets you ship without the key. Inspect the new binary with nm to confirm no Photo Library symbols remain. Most teams find that adding one honest sentence to Info.plist is faster than auditing every transitive dependency, especially when analytics, share, or feedback SDKs reintroduce the symbol on later updates.
    My Expo or React Native project does not have a visible Info.plist, where do I add the key?
    Expo generates Info.plist from the ios.infoPlist section of app.json or app.config.js. Add `"NSPhotoLibraryUsageDescription": "..."` inside that object and run `npx expo prebuild --clean` to regenerate the native project. React Native exposes Info.plist at ios/[ProjectName]/Info.plist. Flutter uses ios/Runner/Info.plist. FlutterFlow exposes the key through App Settings, Permissions, Photo Library. The destination file is the same Info.plist on every framework.
    Can NSPhotoLibraryUsageDescription be localized for international users?
    Yes. Create an InfoPlist.strings file inside each .lproj directory in the Xcode project and add a line with the key name followed by the translated value in double quotes. iOS displays the localized string in the permission alert based on the device language. The English value in Info.plist serves as the fallback for any locale you have not provided a translation for, which keeps users on unsupported languages from seeing a blank prompt.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free