Privacy

    How do you fix ITMS-90683 Missing NSPhotoLibraryUsageDescription?

    Xcode Info.plist editor showing the NSPhotoLibraryUsageDescription key with a one-sentence purpose string filled in for an iOS app preparing to upload to App Store Connect

    If you uploaded a new iOS build to App Store Connect this morning and the validator came back with ITMS-90683 next to your version number, the build did not fail for a code signing or metadata reason. It failed because Apple's automated scan found a call (or a link reference) to a Photo Library API in your binary without a matching NSPhotoLibraryUsageDescription string in your Info.plist. The fix is a single key, but identifying which SDK pulled in the API is usually half the work.

    Short answer

    ITMS-90683 means your compiled iOS binary references the Photo Library APIs without a corresponding NSPhotoLibraryUsageDescription string in Info.plist. The fix is to add the key with a short, honest sentence explaining why the app reads photos, rebuild, and resubmit through App Store Connect. The reference often comes from a third-party SDK (image picker, share plugin, social login, AdMob) rather than your own code, so audit your dependencies before you write the description. According to Apple's NSPhotoLibraryUsageDescription documentation, any iOS app linked on or after iOS 10.0 that accesses the user's photo library must statically declare the intent by including this key in Info.plist with a purpose string.

    What you should know

    • The check is static, not runtime. App Store Connect scans the compiled binary at upload, so removing the call at runtime does not help. The symbol still appears in the linked frameworks.
    • The purpose string is shown to users. When iOS presents the permission prompt, it displays the exact text from your NSPhotoLibraryUsageDescription value, so write something a human would understand.
    • Add-only access has its own key. If the app only writes to the camera roll, use NSPhotoLibraryAddUsageDescription instead of the read key.
    • The trigger is often a transitive SDK. Many builds fail ITMS-90683 even when the app code never touches PhotoKit, because an analytics, ad, or social SDK references the API internally.
    • Generic placeholder strings can re-trigger human review. A description like "Allow access to photos" passes the validator but can earn a Guideline 5.1.1 push-back once a reviewer reads the metadata.

    What is ITMS-90683 and why does App Store Connect raise it?

    The short answer is that ITMS-90683 is the validator code for a missing purpose string. App Store Connect scans every binary you upload, looks for symbols that map to privacy-sensitive APIs, and cross-references them against the keys in your Info.plist. If a symbol references PhotoKit, AssetsLibrary, UIImagePickerController, or related photo APIs and no NSPhotoLibraryUsageDescription key is present, the validator stops the build and emits ITMS-90683.

    The scan happens after Xcode finishes uploading the IPA, before the build appears in TestFlight or the Activity tab. That timing matters: the build never shows up as a candidate version, so you cannot ship it as a hotfix until the warning is resolved. The same scan covers other privacy APIs (camera, microphone, location, contacts, calendar), each tied to its own purpose-string key. The validator reports one missing key at a time, which is why builds often fail ITMS-90683 twice in a row, first for the photo library and then for the camera.

    Apple expanded the strictness of this check around 2018 to catch builds where SDKs silently called Photo Library APIs in code paths the developer believed were disabled. According to a long-running Apple Developer Forums thread on the missing Info.plist key, the check is symbol-based, not control-flow based, so dead code that links against the API still triggers it.

    How do you decide between NSPhotoLibraryUsageDescription and NSPhotoLibraryAddUsageDescription?

    The short answer is by how the app actually uses the photo library. The two keys cover different scopes, and choosing the wrong one is one of the more common reasons a re-submitted build re-trips ITMS-90683.

    KeyWhat iOS prompts forTypical features that require it
    NSPhotoLibraryUsageDescriptionFull read access to photos, videos, albums, and metadataImage pickers, photo gallery views, OCR on user images, EXIF or location reads
    NSPhotoLibraryAddUsageDescriptionWrite-only access to add a new asset to the camera rollSaving a generated image, exporting a video file, archive-only flows

    The official rule from Apple is to declare the minimum scope. If the app only saves a screenshot the user just generated, the Add-only key is sufficient and the permission prompt shows a less alarming message. If the app uses UIImagePickerController to let the user pick an existing photo, the full read key is required, because the underlying API reads the library.

    A build can declare both keys at the same time, which is the right pattern for apps that both read and write. Declaring only the Add key while the binary links against a read API will re-trigger ITMS-90683 on the next upload.

    How do you add the purpose string in Xcode, Expo, React Native, or FlutterFlow?

    The short answer is that every framework eventually writes to Info.plist; the variation is where you express the change in source.

    Native Xcode project. Open Info.plist in the project navigator, add a new row, type NSPhotoLibraryUsageDescription as the key (Xcode auto-completes from a list), and put the purpose string in the value column. Rebuild and re-archive.

    Expo (managed workflow). Add the key under the ios.infoPlist section of app.json or app.config.js:

    {
      "expo": {
        "ios": {
          "infoPlist": {
            "NSPhotoLibraryUsageDescription": "Pick a photo from your library to attach to your post."
          }
        }
      }
    }
    

    Expo's ImagePicker documentation recommends using the photosPermission field of the expo-image-picker config plugin instead, which writes the same key at prebuild time and keeps the message close to the dependency that needs it.

    React Native (bare). Open ios/<AppName>/Info.plist and add the key with a string value, the same way you would in a native Xcode project. Pods can ship their own privacy manifests, but they cannot supply a top-level NSPhotoLibraryUsageDescription on behalf of the host app.

    FlutterFlow. In the FlutterFlow editor, open App Settings, iOS, and add the entry under Custom Info.plist Entries. The exported Xcode project copies the value into Info.plist on the next build. Several FlutterFlow users have reported on the FlutterFlow issue tracker that early project templates did not include the key, so apps that used any image plugin failed ITMS-90683 on first submission.

    Capacitor. Edit ios/App/App/Info.plist directly. The Capacitor Camera plugin documentation requires NSCameraUsageDescription, NSPhotoLibraryUsageDescription, and NSPhotoLibraryAddUsageDescription together for the standard photo-from-library flow.

    Why does ITMS-90683 fire when your code does not touch the photo library?

    The short answer is that one of your dependencies links against the Photo Library framework, even if your code never calls it. The validator scans the entire app bundle, including every static library and dynamic framework, and the symbol resolution does not care which Swift or Objective-C file imported it.

    The common culprits are share extensions, analytics SDKs that capture screenshots, ad SDKs that read media metadata, social login SDKs that offer a set-profile-photo flow, and older versions of OCR or document-scanning libraries. Running nm or otool -L on the compiled framework binaries inside .app/Frameworks/ reveals which library imports the symbol:

    nm -gU MyApp.app/Frameworks/*.framework/* 2>/dev/null | \
      grep -E "PHAsset|PHPhotoLibrary|UIImagePickerController"
    

    If a framework you did not author shows up, the right fix is either to add the purpose string (and accept that the user will see the prompt the first time the SDK triggers it) or to update the SDK to a version that hides the API behind a feature flag. According to the react-native-share issue thread on the same problem, older versions of the share library forced NSPhotoLibraryUsageDescription on every host app even when the share sheet was only used for plain text. Updating to a newer version that gates the photo path behind a runtime opt-in removed the requirement entirely.

    What should the purpose string actually say?

    The short answer is one honest sentence that names the feature, not a generic placeholder. Apple reviewers read these strings during human review, and a vague description can escalate the build to Guideline 5.1.1 even after the validator has passed.

    A useful pattern is feature, reason, scope. "Pick a profile photo from your library" names the feature (profile photo), the reason (let the user choose), and the scope (read access to the library). The same pattern works for write keys: "Save the receipt as an image to your camera roll" names the feature (save receipt), the reason (offline copy), and the scope (write access).

    Avoid the three patterns reviewers consistently flag: blank or boilerplate values (xxxxx, test), brand-only values (just the app name with no verb), and over-broad values (Allow MyApp to access your media, with no feature reference). The string also accepts $(PRODUCT_NAME) as a substitution variable, which Expo's documentation recommends so the app name stays consistent if it ever changes.

    For builders who want an external automated read on a compiled build before it goes through App Review, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning of APK, AAB, and IPA files against OWASP MASVS controls and the App Store Connect validator family, including the missing purpose-string checks.

    What to watch out for

    The trap most teams hit twice is fixing ITMS-90683 by adding the photo library key, then failing on a different ITMS code at the next submission because the same SDK also referenced the camera, microphone, or location APIs. App Store Connect emits one ITMS code per missing key per upload, so the second build that adds only the photo string will fail again on a Missing purpose string in Info.plist - NSCameraUsageDescription line if the SDK touches the camera too. Audit every privacy API the bundle links against in one pass, not one error code at a time.

    The other repeated pattern is the placeholder description that passes the validator but fails human review. A reviewer who reads "Allow access to photos" with no context can request more detail under Guideline 5.1.1, which adds at least one full review cycle. Two weeks lost for a single sentence.

    A myth still in circulation says that setting the key to an empty string suppresses the prompt. It does not. The app crashes when the API is first called, because iOS treats an empty NSPhotoLibraryUsageDescription as undefined behavior. The string must contain real text for the runtime to display the alert.

    Key takeaways

    • Add NSPhotoLibraryUsageDescription to Info.plist with a concrete one-sentence description that names the feature using the photos, then rebuild and resubmit through App Store Connect.
    • Audit every privacy API your SDKs link against in one pass with nm or otool, not one ITMS code at a time, so the next submission does not fail on the next missing key.
    • Prefer NSPhotoLibraryAddUsageDescription when the app only writes new assets, because the prompt is gentler and avoids over-requesting access from the user.
    • Use the feature, reason, scope pattern for the string value, and avoid placeholders that pass the validator but trip Guideline 5.1.1 in human review.
    • For builders who want a second pair of eyes on the compiled binary before submission, PTKD.com (https://ptkd.com) is one of the platforms that runs OWASP MASVS-aligned static scans on APK, AAB, and IPA files and flags missing purpose strings against the App Store Connect rules.
    • #itms-90683
    • #nsphotolibraryusagedescription
    • #info.plist
    • #app store connect
    • #ios
    • #privacy
    • #purpose string

    Frequently asked questions

    Why does ITMS-90683 happen even when I never use the photo library?
    Because the App Store Connect validator scans the entire compiled binary, including every static library and dynamic framework you ship. If any dependency links against PhotoKit, AssetsLibrary, or UIImagePickerController, the symbol shows up in the bundle. The validator does not check whether the code path runs at runtime. It only looks for the symbol. So an analytics or share SDK can trip the warning even when your own code never touches photos.
    Can I set NSPhotoLibraryUsageDescription to an empty string to skip the prompt?
    No. iOS treats an empty value as undefined behavior. The first time any API path tries to access the photo library, the app crashes instead of showing the permission alert. The validator may also reject empty values on upload. The string must contain real human-readable text that describes the feature using the library, because the same value is displayed inside the system permission prompt.
    Do I need NSPhotoLibraryAddUsageDescription as well as NSPhotoLibraryUsageDescription?
    Only when the app both reads and writes the camera roll. If the app only saves a generated image or exports a video, the Add-only key alone is enough and the permission prompt shows a less alarming message. If the app uses an image picker or reads existing photos, the full read key is required. Declaring both is correct for apps that do both, and incorrect for apps that only do one.
    Will Apple human reviewers reject a build that has a placeholder purpose string?
    They can, under Guideline 5.1.1. The automated validator only checks that the key exists with some string value. A human reviewer who sees a description like xxxxx, test, or a single brand name can flag the build for unclear privacy disclosure, which adds a full review cycle. Patterns reported on the Apple Developer Forums show that one-word or generic strings get pushed back even when the validator passes.
    Does this fix apply to React Native, Flutter, Expo, and FlutterFlow projects?
    Yes. Every cross-platform framework that compiles to a native iOS binary goes through the same App Store Connect validator. Expo writes the key from the ios.infoPlist section of app.json. React Native and Flutter use the native ios/[app]/Info.plist file. FlutterFlow exposes it as a Custom Info.plist Entry in App Settings. Capacitor edits ios/App/App/Info.plist directly. The Info.plist destination is the same.

    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