Table: React Native privacy manifest reasons. UserDefaults CA92.1 (react-native and 5 expo modules), FileTimestamp C617.1, 3B52.1, 0A2A.1 (react-native, expo-file-system), SystemBootTime 35F9.1 (react-native, expo-device), DiskSpace 85F4.1, E174.1 (expo-file-system). A missing one brings an ITMS-91053 email; react-native-fs ships no manifest. From our scan of 53 iOS packages, Expo SDK 58 and React Native 0.88.1, 29 October 2026

You upload an iPhone build and a few minutes later App Store Connect emails you ITMS-91053: Missing API declaration, or refuses the build with ITMS-91061: Missing privacy manifest. In a React Native app you rarely wrote the code that caused it: it sits in react-native itself and in the native side of your npm packages. This guide covers what Apple checks, the exact email texts, which React Native and Expo modules use which required-reason APIs (from our own scan of 53 packages), and a PrivacyInfo.xcprivacy that covers them.

How we tested: on 29 October 2026 we made a fresh app with create-expo-app (Expo SDK 58, React Native 0.88.1) and added 20 common modules with npx expo install. A script then grepped every package's iOS source (.m, .mm, .swift, .h, .c, .cpp; test folders skipped) for the APIs on Apple's list and read every PrivacyInfo.xcprivacy the packages ship. We also read the code that merges these files at pod install. This is a source scan, not an App Store upload: it shows which categories each package can trigger. The Apple wording below is quoted from Apple's documentation and from emails developers have posted.

What Apple checks: five API categories and their reason codes

Apple calls these required reason APIs: calls that could be used to fingerprint a device. If your binary calls one, the app's privacy manifest must name the category and at least one approved reason. Since 1 May 2024 App Store Connect doesn't accept apps that use them without one. The full list as of 29 October 2026, from Apple's NSPrivacyAccessedAPIType documentation:

text
category (NSPrivacyAccessedAPICategory...)   APIs that trigger it                       reasons
UserDefaults                                 UserDefaults / NSUserDefaults              CA92.1  this app only
                                                                                        1C8F.1  same App Group
                                                                                        C56D.1  SDK wrapper (SDKs only)
                                                                                        AC6B.1  MDM managed config
FileTimestamp                                creationDate, modificationDate,            DDA9.1  shown to the user, stays on device
                                             NSFileCreationDate/ModificationDate,       C617.1  files in the app or group container
                                             stat, fstat, lstat, fstatat,               3B52.1  files the user picked
                                             getattrlist(bulk/at), fgetattrlist         0A2A.1  SDK wrapper (SDKs only)
SystemBootTime                               systemUptime, mach_absolute_time()         35F9.1  elapsed time, timers
                                                                                        8FFB.1  absolute timestamps of app events
                                                                                        3D61.1  user-submitted bug report
DiskSpace                                    volumeAvailableCapacity...Key,             85F4.1  shown to the user
                                             volumeTotalCapacityKey, systemFreeSize,    E174.1  enough space to write / free up
                                             systemSize, statfs, statvfs, fstatfs,      7D9E.1  user-submitted bug report
                                             fstatvfs, getattrlist                      B728.1  health research apps
ActiveKeyboards                              activeInputModes                           3EC4.1  custom keyboard apps
                                                                                        54BD.1  UI that adapts to the keyboard

Two rules matter for React Native. First, the manifest belongs to the bundle that contains the code. With CocoaPods' default static linking, all your pods are compiled into the app's own executable, so their API use is checked against the app's PrivacyInfo.xcprivacy; a dynamic framework needs its own. Second, the reasons are a promise about what you do with the data: CA92.1 means you only read what your own app wrote, C617.1 that you only look at files inside your container.

The two emails, word for word

ITMS-91053 comes after an upload that uses a category the manifest doesn't declare. The version developers posted before the deadline read:

The useful parts are the file name in quotes (your app's executable, or a framework inside it) and the category list at the end: fix exactly those categories. Since May 2024 the same finding stops the upload instead of being a reminder.

ITMS-91061 is about third-party SDKs, not your code. Since 12 February 2025 a new app, or an update that adds one, can't include an SDK from Apple's list of commonly used SDKs unless that SDK ships its own privacy manifest. A posted example:

Your own manifest can't fix ITMS-91061; only a version of the SDK that ships PrivacyInfo.xcprivacy can. On Apple's list, read 29 October 2026, the SDKs a React Native app is most likely to carry are hermes, the Firebase pods (FirebaseCore, FirebaseMessaging, FirebaseCrashlytics, FirebaseInstallations, GoogleUtilities, GoogleDataTransport, nanopb, FBLPromises, Promises), FBSDKCoreKit and FBSDKLoginKit, GoogleSignIn, AppAuth, GTMAppAuth, GTMSessionFetcher, OneSignal, SDWebImage, Lottie, RealmSwift, Starscream, BoringSSL / openssl_grpc, Protobuf, leveldb and Abseil. The usual cause is an old version pinned in Podfile.lock or a binary someone vendored by hand.

Which React Native and Expo modules use which APIs

What our scan found. "uses" is what the native source calls; the last column is the module's own PrivacyInfo.xcprivacy. Of 53 packages with iOS code, 14 call a required-reason API and 11 ship a manifest. The other 39, among them react-native-reanimated, react-native-screens, react-native-svg, react-native-webview, expo-image, expo-router and expo-camera, call none of them in their own source.

text
package (version)                            uses                              ships a manifest with
react-native 0.88.1                          UserDefaults, FileTimestamp       UserDefaults CA92.1, FileTimestamp C617.1,
                                                                               SystemBootTime 35F9.1
expo-file-system 58.0.13                     DiskSpace, FileTimestamp          FileTimestamp 0A2A.1 3B52.1, DiskSpace 85F4.1 E174.1
expo-constants 58.0.14                       UserDefaults                      UserDefaults CA92.1
expo-device 58.0.5                           SystemBootTime                    SystemBootTime 35F9.1
expo-application 58.1.2                      FileTimestamp                     FileTimestamp C617.1
expo-localization 58.0.5                     UserDefaults                      UserDefaults CA92.1
expo-notifications 58.1.7                    UserDefaults                      UserDefaults CA92.1
expo-system-ui 58.0.7                        UserDefaults                      UserDefaults CA92.1
expo-eas-client 58.0.2 (with expo-updates)   UserDefaults                      UserDefaults CA92.1
@react-native-async-storage 2.2.0            FileTimestamp                     FileTimestamp C617.1
react-native-device-info 15.0.2              DiskSpace, FileTimestamp,         DiskSpace 85F4.1, FileTimestamp C617.1,
                                             UserDefaults                      UserDefaults CA92.1, SystemBootTime 35F9.1
expo-updates 58.0.19                         UserDefaults                      none
@react-native-firebase/app 27.1.0            UserDefaults                      none (the Firebase pods carry their own)
react-native-fs 2.20.0                       DiskSpace, FileTimestamp          none

Watch the last three. expo-updates keeps a config override in UserDefaults.standard; @react-native-firebase/app keeps its preferences in UserDefaults; react-native-fs reads NSFileCreationDate and NSFileModificationDate for stat() and NSFileSystemFreeSize for getFSInfo(). None of them ships a manifest, so those uses are declared only if another package or your own file already covers the category.

Some calls a grep of node_modules can't see. The Hermes engine, MMKV core (react-native-mmkv 4 pulls in the MMKVCore pod) and the Sentry and Firebase native SDKs arrive as pods or prebuilt binaries at pod install, with their own manifests. After pod install, find ios/Pods -name PrivacyInfo.xcprivacy lists every manifest in the build.

How the app manifest is put together at pod install

Expo's guide warns that Apple doesn't reliably read the PrivacyInfo files of static CocoaPods dependencies, so a pod's manifest alone may not satisfy the check on your executable. React Native works around this. In 0.88.1, react_native_pods.rb has privacy_file_aggregation_enabled on by default, and Expo's Podfile template turns it off only if Podfile.properties.json sets apple.privacyManifestAggregationEnabled to false. At pod install, scripts/cocoapods/privacy_manifest_utils.rb:

text
1. finds the app target's PrivacyInfo.xcprivacy (or creates one next to Info.plist)
2. reads every PrivacyInfo.xcprivacy that a pod lists in its resource_bundles
3. adds React Native core's own three: FileTimestamp C617.1, UserDefaults CA92.1, SystemBootTime 35F9.1
4. merges in whatever your existing file already declares
5. writes the union back and adds the file to the app target's resources

Step 2 is the catch: only pods that list their manifest in resource_bundles count. All 11 manifest-shipping packages in our scan do (expo-constants' podspec, for example, has 'ExpoConstants_privacy' => ['PrivacyInfo.xcprivacy']); a package with no manifest adds nothing. On Expo, ios.privacyManifests from app.json is written into the same file during prebuild, before pod install, so your entries and the merged ones end up together.

Worked example 1: a default Expo app

The default template brings react-native, expo-constants, expo-device, expo-system-ui and, through the expo package itself, expo-file-system. Running the same merge on our scan results gives the manifest below: four categories, seven reasons. The template's other iOS modules call none of the APIs, so nothing is left undeclared and this app should not get an ITMS-91053 email.

xml
<?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>NSPrivacyAccessedAPICategoryFileTimestamp</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>C617.1</string>
				<string>0A2A.1</string>
				<string>3B52.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>CA92.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>35F9.1</string>
			</array>
		</dict>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>85F4.1</string>
				<string>E174.1</string>
			</array>
		</dict>
	</array>
	<key>NSPrivacyCollectedDataTypes</key>
	<array/>
	<key>NSPrivacyTracking</key>
	<false/>
</dict>
</plist>

One quirk: 0A2A.1 comes from expo-file-system's manifest, and Apple says only third-party SDKs may declare it, yet the merge copies it into the app's file. If you write the file by hand instead, keep C617.1 and 3B52.1 (and DDA9.1 if you show file dates) and leave 0A2A.1 to the module.

Worked example 2: a bare React Native app with react-native-fs

Take a React Native CLI app without Expo, with @react-native-async-storage/async-storage and react-native-fs. The merge gives react-native's three entries plus async-storage's FileTimestamp C617.1. react-native-fs adds no manifest but calls NSFileSystemFreeSize, a DiskSpace API, and nothing declares DiskSpace: the upload gets ITMS-91053 naming NSPrivacyAccessedAPICategoryDiskSpace. FileTimestamp is already covered by C617.1, which fits if you only stat files in your own container. Add DiskSpace with E174.1 if the app checks free space before writing, or 85F4.1 if it shows free space to the user:

xml
<dict>
  <key>NSPrivacyAccessedAPIType</key>
  <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
  <key>NSPrivacyAccessedAPITypeReasons</key>
  <array>
    <string>E174.1</string>
  </array>
</dict>

Put it in the NSPrivacyAccessedAPITypes array of ios/<YourApp>/PrivacyInfo.xcprivacy. The next pod install keeps it, because the merge starts from your file.

A manifest that passes, step by step

For an Expo app (prebuild or continuous native generation), declare everything in app.json so it survives every prebuild. This one covers the default template plus expo-updates and react-native-fs:

json
{
  "expo": {
    "ios": {
      "privacyManifests": {
        "NSPrivacyAccessedAPITypes": [
          { "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
            "NSPrivacyAccessedAPITypeReasons": ["CA92.1"] },
          { "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
            "NSPrivacyAccessedAPITypeReasons": ["C617.1"] },
          { "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime",
            "NSPrivacyAccessedAPITypeReasons": ["35F9.1"] },
          { "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryDiskSpace",
            "NSPrivacyAccessedAPITypeReasons": ["E174.1"] }
        ]
      }
    }
  }
}

Then check before you upload:

bash
# 1. which packages in node_modules ship a manifest
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*'

# 2. which packages call a required-reason API in their iOS code (rough grep)
grep -rlE 'NSUserDefaults|UserDefaults\.standard|NSFileSystemFreeSize|volumeAvailableCapacity|systemUptime|mach_absolute_time|NSFileModificationDate|NSFileCreationDate|activeInputModes' \
  node_modules/*/ios node_modules/@*/*/ios node_modules/*/*.m 2>/dev/null | grep -vi test | cut -d/ -f2-3 | sort -u

# 3. after npx expo prebuild -p ios and pod install (on a Mac): the merged result
plutil -p ios/*/PrivacyInfo.xcprivacy

Any category that step 2 turns up and step 3 lacks is your next ITMS-91053. Last, archive in Xcode (Product > Archive) and choose Generate Privacy Report in the Organizer: it shows the manifests of every bundle merged, and it is what you copy into the App Privacy answers in App Store Connect. That covers the other half of the manifest, NSPrivacyCollectedDataTypes, which this article doesn't go into.

When the email still arrives

text
symptom                                     likely cause                                fix
ITMS-91053, category X, file "YourApp"      a pod without a manifest calls X            add X with the reason that fits to
                                            (react-native-fs, expo-updates,             app.json privacyManifests or
                                            @react-native-firebase/app)                 ios/YourApp/PrivacyInfo.xcprivacy
ITMS-91053 although your file lists X       the file isn't in the app target's          check Build Phases > Copy Bundle
                                            resources (hand-made file, merge off)       Resources; run pod install again
ITMS-91053 for "Frameworks/Foo.framework"   a dynamic framework without its own         update that SDK; its manifest must
                                            manifest (use_frameworks! :dynamic)         sit inside the framework
ITMS-91061, "Frameworks/X.framework/X"      an SDK from Apple's list is too old         update the pod (pod update X), check
                                                                                        Podfile.lock, rebuild

Expo's guide suggests one more route: upload once, read the email, and add exactly the categories it lists; the email usually comes within minutes. It works, but each round costs a full release build and upload, and the scan above finds the same gaps before the build.

For the rest of the iPhone upload path, see Code signing for React Native teams: certificates, profiles and upload keys and TestFlight in practice: tester limits, 90-day build expiry and beta review.

Build your React Native or Expo app on aipps.dev: prebuild and pod install run on our Mac builders, so the manifests are merged as described here, and aipps build -p ios --submit sends the build to TestFlight.

Open the console

Sources: Apple, Describing use of required reason API and NSPrivacyAccessedAPIType (categories, APIs and reason codes), read 29 October 2026; Apple, Third-party SDK requirements (the SDK list), same day; Expo docs, Privacy manifests guide (ios.privacyManifests, the static pods note); ITMS-91053 and ITMS-91061 email texts as posted on the Apple Developer Forums (threads 748765 and 774634); react-native 0.88.1 scripts/cocoapods/privacy_manifest_utils.rb and react_native_pods.rb, expo-template-bare-minimum 58.0.30 Podfile, @expo/config-plugins ios/PrivacyInfo.js; our scan of a create-expo-app project (Expo SDK 58, React Native 0.88.1) with 20 added modules, 29 October 2026.