Privacy

    How do I fix ITMS-91053 for the UserDefaults API?

    App Store Connect rejection email referencing ITMS-91053 and NSPrivacyAccessedAPICategoryUserDefaults

    You uploaded a build to App Store Connect, the processing email landed within minutes, and the body referenced ITMS-91053 with NSPrivacyAccessedAPICategoryUserDefaults flagged. The app archives in Xcode, the previous TestFlight build was fine, but today the upload is held with the status Invalid Binary. The question is what that one line in the email is really pointing at, which call inside the binary is responsible, and the smallest plist change that lets the next upload process cleanly.

    Short answer

    App Store Connect rejects builds that read or write user defaults without declaring those calls in a PrivacyInfo.xcprivacy file. The fix is a privacy manifest entry with NSPrivacyAccessedAPICategoryUserDefaults paired with an approved reason code: CA92.1 for defaults used only within the app, 1C8F.1 for defaults shared across an App Group, or C56D.1 for third-party SDKs that wrap the UserDefaults API behind a function the host app calls. Apple has enforced this rule for new submissions since 1 May 2024, per the Apple Developer privacy manifest documentation.

    What you should know

    Why does App Store Connect return ITMS-91053 for UserDefaults?

    The short answer is that Apple now scans every uploaded binary for symbols associated with a defined set of required reason APIs, and it refuses to process a build that references one of those symbols without a matching declaration in a privacy manifest. The user defaults group covers the NSUserDefaults class in Objective-C, the Swift UserDefaults wrapper, the +standardUserDefaults singleton, and any per-suite initializer like initWithSuiteName.

    The scan does not read your Swift or Objective-C source. It reads the compiled Mach-O. A project that builds and runs cleanly can still fail on upload because a linked framework reads a preference at startup and the symbol survived stripping. Apple's TN3183 technote describes the rule as an audit of API usage, not a behavioural check, which is why the diagnostic appears during ingestion rather than at runtime.

    Which reason code should I pick for user defaults?

    Apple publishes three approved reasons under NSPrivacyAccessedAPICategoryUserDefaults. Each one matches a different real-world usage pattern, and a single manifest entry can list more than one when the app legitimately spans cases.

    The most common reason is CA92.1. It covers reading and writing user defaults that are accessible only to the app itself. A login flag stored under UserDefaults.standard, a feature toggle behind a key like has_seen_onboarding, or a saved theme preference all sit here.

    1C8F.1 covers user defaults shared inside an App Group. When the iOS app and a widget, watch extension, or App Clip read the same NSUserDefaults instance through an initializer like initWithSuiteName('group.com.example.shared'), this is the correct code. Apple's Describing use of required reason API page lists this as the distinguishing case for cross-process access.

    C56D.1 is restricted to third-party SDKs that wrap the user defaults API behind a function the host app calls. The SDK ships this declaration in its own manifest, and the derived information cannot be used for the SDK's own purposes. An app developer does not use C56D.1 for first-party code.

    Cases reported by developers show that submitting CA92.1 when 1C8F.1 would have been more accurate does not block the upload. App Store Connect does not police the alignment between the description and the call at ingestion. The audit only checks that one approved reason is present for the category. Picking the reason that genuinely matches the call is still the safer practice, because a later human review pass can ask about it.

    How do I add the entry to PrivacyInfo.xcprivacy?

    The file goes into the bundle of the target that calls the API. For a plain iOS app with no embedded frameworks, that is the app target, and PrivacyInfo.xcprivacy sits at the root of the .app folder. Add it via File, New, File from Template, App Privacy under the Resource section. Xcode generates an empty plist and registers it with the current target.

    The one step developers miss is target membership. Open the File Inspector on the new manifest, scroll to Target Membership, and confirm the app target box is ticked. A manifest in the project navigator but outside target membership never reaches the .app bundle, and ITMS-91053 returns on the next upload.

    The smallest valid entry for a user-defaults-only fix is one dictionary inside the NSPrivacyAccessedAPITypes array, with NSPrivacyAccessedAPIType set to NSPrivacyAccessedAPICategoryUserDefaults and NSPrivacyAccessedAPITypeReasons set to an array containing one or more reason strings.

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
      <key>NSPrivacyAccessedAPITypes</key>
      <array>
        <dict>
          <key>NSPrivacyAccessedAPIType</key>
          <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
          <key>NSPrivacyAccessedAPITypeReasons</key>
          <array>
            <string>CA92.1</string>
          </array>
        </dict>
      </array>
    </dict>
    </plist>
    

    If the same target also reads disk space, file timestamps, or system boot time, those go in as sibling dictionaries inside the same NSPrivacyAccessedAPITypes array. After editing the plist, archive the project, right-click the .ipa in Finder and Show Package Contents, and confirm PrivacyInfo.xcprivacy sits at the top level of the .app folder before the next upload.

    How do I find which framework calls UserDefaults?

    A build that compiles fine but fails on upload usually carries the call inside a CocoaPods, Swift Package Manager, or vendored .xcframework dependency. There are three reliable paths to identify the responsible SDK.

    The first is symbol grep. After archiving, open the .ipa, navigate to Payload/.app/Frameworks, and run nm -u on each binary to list referenced symbols. A match on OBJC_CLASS$_NSUserDefaults, +standardUserDefaults, or initWithSuiteName narrows the candidate list quickly. TN3183 recommends this approach for finding the responsible framework.

    The second is the SDK's own changelog. Most major vendors began shipping privacy manifests in spring 2024. Firebase, Google Mobile Ads, AppsFlyer, Adjust, Sentry, Branch, and Lottie all published manifest-bearing releases by mid-2024. The Firebase team tracked the rollout publicly in issue 12557 on the firebase-ios-sdk repository. If the Podfile or Package.resolved is pinned to a pre-April 2024 version, bumping to a current release usually clears the rejection without manual plist work.

    The third is process of elimination. Comment out a suspect SDK initialization, archive, and upload to a separate test app record. If the rejection disappears, that SDK is the source. The approach is slow but useful for closed-source dependencies without a public changelog.

    Which UserDefaults APIs trigger the scan?

    The table below collects the most common entry points that App Store Connect's scanner flags under NSPrivacyAccessedAPICategoryUserDefaults, based on Apple's published reason API list.

    API or symbolLanguage or frameworkTypical caller
    UserDefaults.standardSwift FoundationFeature flags, onboarding state
    +standardUserDefaultsObjective-C NSUserDefaultsLegacy code paths
    init(suiteName:)Swift FoundationApp Group preferences
    initWithSuiteName:Objective-C NSUserDefaultsWidget and watch extensions
    objectForKey:, setObject:forKey:NSUserDefaults instanceGeneric read and write
    registerDefaults:NSUserDefaultsInitial values on first launch

    Apple groups every entry above under a single category, so one reason code on a single dictionary entry covers any combination of these calls. There is no separate code per symbol.

    What to watch out for

    Key takeaways

    Frequently asked questions

    Is ITMS-91053 a hard rejection or just a warning?
    Since 1 May 2024 it is a hard rejection for new App Store submissions and TestFlight builds. Before that date the same message arrived as informational email and the build still processed. Today the binary is held in App Store Connect with the status Invalid Binary, and a corrected manifest plus a higher build number is required to clear it and ship the next release.
    Which reason code do I pick for UserDefaults?
    Pick the code that matches the actual call. CA92.1 covers user defaults read or written only inside the app itself, the most common case. 1C8F.1 covers defaults shared across an App Group with widgets, watch apps, or App Clips. C56D.1 is reserved for third-party SDKs that wrap UserDefaults behind a function the host app calls. App Store Connect accepts any of the three at ingestion.
    Why does ITMS-91053 still appear after I added the manifest?
    Three common reasons. First, the PrivacyInfo.xcprivacy file is not part of the target that ships; check Target Membership in the File Inspector. Second, the manifest is in your app but the UserDefaults call lives inside a third-party framework that needs its own manifest. Third, the build number was not incremented, so App Store Connect kept the old binary in processing instead of ingesting the new one.
    Do I need a manifest if I only call UserDefaults.standard once?
    Yes. The required reason rule applies to any call against UserDefaults or NSUserDefaults, including a single read of a feature flag in AppDelegate. The audit reads the compiled Mach-O for the symbol, not the call frequency. A single dictionary entry with NSPrivacyAccessedAPICategoryUserDefaults paired with CA92.1 is enough to clear the rejection for an app that touches defaults only for its own state.
    Will adding a privacy manifest change anything users see?
    No. The privacy manifest is metadata read by App Store Connect during ingestion and surfaced in the Privacy Nutrition Label on the App Store listing. Runtime behaviour does not change, no new permission prompt appears, and existing users see no difference. The manifest only affects what Apple's automated review pipeline accepts and what the public listing displays for privacy disclosures.

    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