Blogs

React Native and Expo Privacy Manifest Guide: Fix ITMS-91053 Without Guessing

Last updated: September 27, 2026

React Native privacy-manifest problems are rarely solved by one universal app.json block. The correct fix depends on whether the declaration belongs to your app, an Expo module, a CocoaPod, or another embedded framework—and whether Apple reported ITMS-91053, ITMS-91056, or ITMS-91061.

This guide covers Expo projects that use Continuous Native Generation, projects with committed ios directories, and bare React Native apps. The goal is to trace Apple's evidence through the JavaScript dependency graph and the final iOS archive before changing a declaration.

Quick answer: use expo.ios.privacyManifests or an app-target PrivacyInfo.xcprivacy for declarations that genuinely belong to your app. If ITMS-91061 names a third-party SDK, upgrade or replace that SDK; do not use the app manifest as a substitute for the SDK's missing file.

Start with the error code, not the framework

ErrorMeaningTypical React Native investigation
ITMS-91053Apple detected a Required Reason API without an accepted declarationApp code, Expo config, native module, or aggregated static-pod reasons
ITMS-91056Apple could not accept a manifest at a named pathParse and validate the exact app, pod, or framework manifest
ITMS-91061A privacy-impacting third-party SDK is missing its own manifestUpgrade, replace, remove, or fix the SDK package

Apple's third-party SDK requirements include many components common in React Native builds: hermes, Firebase pods, FBLPromises, GoogleUtilities, Lottie, OneSignal, Reachability, and others. An npm package can introduce several of those native dependencies transitively.

Paste the rejection email into the App Store Privacy Error Checker before editing files. It extracts the SDK names, paths, error codes, and API categories locally in your browser. Preserve those fields in the release ticket because dependency changes can make the original evidence hard to reconstruct.

Identify which React Native workflow you have

The source of truth differs by project type.

Expo with Continuous Native Generation

If the native project is generated from app configuration, treat app.json or app.config.ts and config plugins as source. Direct edits inside a generated ios directory can disappear on the next prebuild.

Expo supports an ios.privacyManifests configuration object. The official Expo privacy manifest guide documents the supported structure.

Expo with a committed ios directory

If the team commits and maintains ios, both app configuration and native Xcode state can matter. Check whether a config plugin rewrites PrivacyInfo.xcprivacy, whether the file is included in the application target, and whether a prebuild step is part of CI.

Bare React Native

Create or edit PrivacyInfo.xcprivacy in Xcode, add it to the application target, and verify that it appears in Copy Bundle Resources. Native libraries still need their own manifests when the requirement applies to their SDK bundles.

Step 1: map the email SDK to npm and CocoaPods

Start with the JavaScript dependency tree:

npm ls --all
npm explain react-native-some-package

For Yarn projects, use the equivalent ownership query:

yarn why react-native-some-package

Then inspect the native resolution:

grep -n "PODS:" ios/Podfile.lock
grep -n "hermes\|FirebaseCore\|GoogleUtilities\|Reachability" ios/Podfile.lock

Replace those example names with the SDK Apple reported. A JavaScript wrapper, its pod, and the framework inside the app can all have different names. The useful chain looks like this:

npm package → podspec → resolved pod → framework path in Apple's email

Do not stop when you find a similarly named package. Confirm the relationship in the package's podspec or the generated Pods project.

Step 2: find the manifests available in source dependencies

Inventory the checkout:

find ios node_modules -name PrivacyInfo.xcprivacy -print 2>/dev/null

Then focus on the package and pod that own the reported framework:

find node_modules/react-native-some-package -name PrivacyInfo.xcprivacy -print
find ios/Pods -path "*SomePod*" -name PrivacyInfo.xcprivacy -print

A manifest under node_modules proves that the vendor published a file. It does not prove that CocoaPods copied the file into the final application. Inspect the package's .podspec for resource_bundles, resources, or another supported resource declaration.

Step 3: configure an app-owned manifest in Expo

When your own application code uses a Required Reason API, Expo can generate the app manifest from configuration. This example declares app-specific UserDefaults access:

{
  "expo": {
    "ios": {
      "privacyManifests": {
        "NSPrivacyAccessedAPITypes": [
          {
            "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
            "NSPrivacyAccessedAPITypeReasons": ["CA92.1"]
          }
        ]
      }
    }
  }
}

This is an example, not a drop-in fix. Use it only if the application behavior satisfies Apple's conditions for that approved reason. Read the current Required Reason API documentation and use the Required Reason API Checker to catch a category/code mismatch.

After changing Expo dependencies, align packages with the installed Expo SDK:

npx expo install --fix

If your workflow regenerates native projects, review all native changes produced by prebuild. Do not run a clean regeneration over uncommitted native customizations. The generated PrivacyInfo.xcprivacy should be inspected before the release build.

Step 4: configure an app-owned manifest in bare React Native

In Xcode:

  1. Open the .xcworkspace, not only the .xcodeproj, when the project uses CocoaPods.
  2. Add a Privacy Manifest resource named exactly PrivacyInfo.xcprivacy.
  3. Select the main application target when the declaration belongs to the app.
  4. Confirm target membership and Copy Bundle Resources.
  5. Open the file as source and review its XML types.

Validate it from the terminal:

plutil -lint ios/MyApp/PrivacyInfo.xcprivacy
plutil -p ios/MyApp/PrivacyInfo.xcprivacy

An app-level file should describe the app's own practices. It should not become a dumping ground for declarations copied from every package in node_modules.

Static CocoaPods aggregation is not the same as ITMS-91061

Expo documents a real packaging edge case: Apple does not always parse every privacy manifest included by static CocoaPods dependencies, so projects may need to aggregate Required Reason API entries for submission tooling. Expo also exposes manifest-aggregation support in current build tooling.

That packaging issue must not be confused with Apple's SDK ownership rule:

  • For ITMS-91053, an accurate aggregate declaration can help surface required reasons used through static native dependencies.
  • For ITMS-91061, Apple is reporting that a listed SDK bundle lacks its required manifest. The durable fix is still an updated or corrected SDK that ships its own manifest.

If the email names Frameworks/SomeSDK.framework/SomeSDK, do not assume that adding SomeSDK reasons to the app manifest satisfies the per-SDK requirement. Follow the ITMS-91061 troubleshooting guide and the SDK vendor's release notes.

Step 5: repair the responsible package

Use the least speculative option:

  1. Upgrade the direct package that brings in the native SDK.
  2. Confirm the resolved pod version changes in Podfile.lock.
  3. Run the package's supported installation command and regenerate pods.
  4. If the package is abandoned, replace or remove it.
  5. If you own a fork, add the manifest to the SDK target or resource bundle and document each reason.

For a CocoaPods project, install the resolved dependencies after reviewing version constraints:

cd ios
pod install --repo-update
cd ..

Avoid deleting Podfile.lock as a first response. That can update the entire native dependency graph and obscure which change fixed the privacy error.

Step 6: validate ITMS-91056 at the named path

If Apple reports ITMS-91056, validate the exact file named in the email. A valid app manifest cannot compensate for an invalid file embedded in a framework.

Check for:

  • A dictionary at the plist root.
  • Supported top-level keys only.
  • Arrays, dictionaries, strings, and booleans in the expected positions.
  • No empty reason strings or placeholder values.
  • Tracking domains written as hostnames, without a scheme, path, query, fragment, or trailing slash.
  • Reason codes approved for the declared category.

Use Apple's TN3181 together with the local PrivacyInfo.xcprivacy Validator.

Step 7: inspect the release archive

The archive is the submission unit, so it is the final evidence. After producing a release archive or IPA, list every manifest:

find /path/to/MyApp.xcarchive -name PrivacyInfo.xcprivacy -print

For an IPA copy that you can safely unpack into a temporary directory:

work_dir="$(mktemp -d)"
unzip -q MyApp.ipa -d "$work_dir"
find "$work_dir/Payload" -name PrivacyInfo.xcprivacy -print

Then validate each discovered file:

find "$work_dir/Payload" -name PrivacyInfo.xcprivacy -print0 \
  | xargs -0 -n1 plutil -lint

Confirm that the framework named by Apple contains its expected manifest. If the file exists in node_modules but not in the archive, investigate pod resources, config plugins, build phases, and CI generation—not the reason text itself.

Frequent React Native mistakes

  • Editing generated native files without updating the Expo configuration that owns them.
  • Adding a reason code because another app uses it, without confirming the approved use case.
  • Treating npm update as proof that the transitive pod changed.
  • Copying a third-party SDK declaration into the app manifest for ITMS-91061.
  • Validating only the app manifest when Apple names a framework manifest.
  • Adding https://analytics.example.com/path/ as a tracking domain instead of the bare hostname.
  • Sending the full rejection email or manifest to an untrusted online service.

Release checklist

  • The project type—Expo generated, Expo native, or bare React Native—is documented.
  • The Apple email path is mapped to an npm package and resolved pod.
  • App-owned declarations live in the app configuration or app target.
  • Third-party SDK manifests remain with the owning SDK.
  • Every manifest passes property-list and schema checks.
  • The exact release archive contains the expected files.
  • CI regenerates the same native state verified locally.
  • A developer confirms every reason code against actual behavior.

This process reduces repeated uploads, but it cannot guarantee App Review approval and is not legal advice. Apple documentation and the behavior of the shipped native code remain authoritative.

Check the App Store email and PrivacyInfo.xcprivacy together without uploading either one.

Check My Privacy Manifest