Fixing iOS App Store Rejections for Missing Privacy Manifest (PrivacyInfo.xcprivacy) in React Native Apps
An app that sailed through App Store review for years suddenly gets flagged for a missing Privacy Manifest, with nothing changed in your own code. The real culprit is almost always a third-party dependency several layers deep — here's how to find it and fix it.
The Problem
A React Native app that's been sailing through App Store review for years suddenly gets rejected (or the build gets flagged before review even starts) with a message about a missing or incomplete Privacy Manifest — PrivacyInfo.xcprivacy — or about "required reason" API usage that isn't declared. Nothing in the app's own code changed; the trigger is almost always a third-party SDK or React Native module the app depends on.
Why It Happens
Apple's manifest requirement is transitive
Since Apple started enforcing privacy manifests, every SDK (including ones bundled deep inside React Native modules like analytics libraries, ad SDKs, or even some native modules for camera/storage access) that uses a "required reason" API — things like accessing the disk timestamp, active keyboard type, or user defaults — must ship its own PrivacyInfo.xcprivacy declaring why. If even one dependency several layers deep is missing this, App Store Connect flags the whole build.
React Native's own native modules aren't always exempt
Modules that touch things like UserDefaults (for AsyncStorage-style persistence) or file timestamps trigger the same requirement, and older or less-maintained community packages frequently haven't been updated with their own manifest yet.
The error message doesn't name the actual culprit
App Store Connect's rejection or warning email typically names the "required reason" API category (e.g. "File timestamp APIs") without naming which specific framework in the app bundle is using it, which is why this feels mysterious the first time it happens.
The Fix
1. Find every third-party binary framework actually shipping in the build
find ios/Pods -name "*.xcframework" -o -name "*.framework" | sort
Cross-reference this list against each dependency's changelog/GitHub for privacy manifest support — most major SDKs (Firebase, major ad networks, crash reporters) published manifest updates once Apple's enforcement date was announced, so upgrading the dependency is often the actual fix, not writing a manifest yourself.
2. Add your own app-level PrivacyInfo.xcprivacy for APIs your own code (or an unmaintained dependency) uses
<?xml version="1.0" encoding="UTF-8"?>
<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>
Add this file to the main app target in Xcode (not just a Pod target), and pick the reason code that genuinely matches how the API is used — Apple validates against a fixed list of allowed reasons per API category, and an invented or mismatched code gets rejected just as fast as a missing manifest.
3. For unmaintained dependencies, patch the manifest in, don't fork the whole package
If a small community-maintained native module is the culprit and hasn't shipped its own manifest, patch-package (or a Podfile post_install hook) can inject a PrivacyInfo.xcprivacy file into that pod's directory during pod install, without maintaining a full fork of the dependency.
4. Re-check after every dependency upgrade, not just before submission
Because this requirement is transitive through the whole dependency tree, a routine SDK version bump can silently reintroduce an undeclared API. Make the xcframework audit from step 1 part of the release checklist, not a one-time fix.
Why This Works
None of this is about hiding what the app does — it's about making an already-true statement machine-readable in the format Apple's static analysis expects. Once every framework in the bundle (yours and third-party) declares its required-reason API usage with a matching manifest entry, there's nothing left for the automated privacy check to flag.
Conclusion
A Privacy Manifest rejection almost never means your own code did something wrong — it means some framework in the dependency tree, often several layers deep, uses a "required reason" API without declaring it. Audit the actual frameworks shipping in the build, upgrade the ones with published manifest support, and patch a manifest in for the ones that don't, and the rejection clears without changing what the app actually does.
