iOS Privacy Manifest: How to Write PrivacyInfo.xcprivacy
Last updated
An iOS privacy manifest is a file named PrivacyInfo.xcprivacy at the root of your app bundle. It declares what data the app collects, whether it tracks users, and why it calls “required reason APIs” such as UserDefaults and file timestamps. If your code uses any of these APIs, and nearly every app uses UserDefaults, the app needs a manifest or the upload is rejected with ITMS-91053. Write one reason code per API category your source uses, add the file to the app target, then check it inside the .ipa. Rules follow Apple’s documentation as of October 2026.
Before you start, have a project that builds a Release package, with KXBuild and the VS Code extension installed. If you haven’t installed them, follow the install page.
When do you need an iOS privacy manifest?
Apple introduced privacy manifests at WWDC23 and turned them into an App Store requirement in stages.
| Effective date | Requirement | What happens otherwise | Source |
|---|---|---|---|
| May 1, 2024 | Apps that use required reason APIs must state the reasons in a privacy manifest | App Store Connect rejects the upload with an ITMS-91053 email | Describing use of required reason API |
| November 12, 2024 | Privacy manifests in apps submitted for review must be valid | ITMS-91056 email listing the invalid file paths | TN3181 |
| February 12, 2025 | Commonly used third-party SDKs must include a valid privacy manifest | ITMS-91061 email listing the SDKs that lack one | Adding a privacy manifest |
In short, any iOS app submitted in 2026 that uses UserDefaults, including SwiftUI’s @AppStorage, needs a privacy manifest. If your own code uses none of these APIs and collects no data, you can leave it out, but the third-party SDKs you use still need their own manifests.
The two halves of the manifest apply to different platforms. Collected data types are required on all platforms. Required reason APIs are required on iOS, iPadOS, tvOS, visionOS and watchOS.
What does PrivacyInfo.xcprivacy contain?
PrivacyInfo.xcprivacy is a plist and must have exactly that name. The top-level dictionary has four keys.
| Key | Type | Purpose |
|---|---|---|
NSPrivacyTracking |
Boolean | Whether the app uses data for tracking as defined by App Tracking Transparency |
NSPrivacyTrackingDomains |
Array (String) | Domains used for tracking. Only include it when NSPrivacyTracking is true |
NSPrivacyCollectedDataTypes |
Array (Dictionary) | Collected data types, one dictionary per type |
NSPrivacyAccessedAPITypes |
Array (Dictionary) | Required reason API categories used, one dictionary per category |
Requests to domains listed in NSPrivacyTrackingDomains fail when the user hasn’t granted tracking permission. List only domains or subdomains, with no path, query string or trailing slash.
Each dictionary in NSPrivacyAccessedAPITypes has two keys.
NSPrivacyAccessedAPIType, a String with the category name, for exampleNSPrivacyAccessedAPICategoryUserDefaults.NSPrivacyAccessedAPITypeReasons, an Array of reason codes for that category, for exampleCA92.1.
Each dictionary in NSPrivacyCollectedDataTypes has four keys.
NSPrivacyCollectedDataType, a String with the data type, for exampleNSPrivacyCollectedDataTypeEmailAddress.NSPrivacyCollectedDataTypeLinked, a Boolean for whether the data is linked to the user’s identity.NSPrivacyCollectedDataTypeTracking, a Boolean for whether the data is used for tracking.NSPrivacyCollectedDataTypePurposes, an Array of purposes, for exampleNSPrivacyCollectedDataTypePurposeAppFunctionality.
What are the five required reason API categories?
Apple groups APIs that could be used for device fingerprinting into five categories. The table lists the APIs in each category and the common reason codes, based on the NSPrivacyAccessedAPIType reference.
| Category | APIs (selection) | Common reason codes |
|---|---|---|
NSPrivacyAccessedAPICategoryUserDefaults |
UserDefaults, including @AppStorage |
CA92.1, 1C8F.1 |
NSPrivacyAccessedAPICategoryFileTimestamp |
creationDate, modificationDate, contentModificationDateKey, creationDateKey, stat, fstat, lstat, getattrlist and others |
C617.1, DDA9.1, 3B52.1 |
NSPrivacyAccessedAPICategorySystemBootTime |
systemUptime, mach_absolute_time() |
35F9.1, 8FFB.1 |
NSPrivacyAccessedAPICategoryDiskSpace |
volumeAvailableCapacityKey, volumeAvailableCapacityForImportantUsageKey, volumeTotalCapacityKey, systemFreeSize, statfs, statvfs and others |
E174.1, 85F4.1 |
NSPrivacyAccessedAPICategoryActiveKeyboards |
activeInputModes |
54BD.1, 3EC4.1 |
Choose reason codes
Pick codes that match how you actually use the API. You can list more than one, but each must match what the app does. These are the key points from Apple’s definitions.
| Reason code | Category | Use case | Restriction |
|---|---|---|---|
CA92.1 |
UserDefaults | Read and write data that only the app itself can access | Can’t read data written by other apps or the system |
1C8F.1 |
UserDefaults | Read and write data shared by apps, extensions and App Clips in the same App Group | Can’t read or write data outside the App Group |
C617.1 |
File timestamp | Read timestamps, size or other metadata of files in the app container, App Group container or the app’s CloudKit container | Only files in those containers |
DDA9.1 |
File timestamp | Show file timestamps to the user | The data can’t leave the device |
3B52.1 |
File timestamp | Access metadata of files or folders the user explicitly granted access to, for example through a document picker | Only files the user granted |
35F9.1 |
System boot time | Measure time between events in the app, or implement timers | Only the elapsed time can leave the device |
8FFB.1 |
System boot time | Calculate absolute timestamps for events in the app, such as UIKit or AVFAudio events | The boot time itself can’t leave the device |
E174.1 |
Disk space | Check whether there’s enough space to write files, or delete files when space is low | The app must behave differently based on the space in a way the user can notice |
85F4.1 |
Disk space | Show disk space to the user | The data can’t leave the device |
54BD.1 |
Active keyboards | Show the correct custom UI for the current keyboard | The app must have text input and change visibly based on the keyboard |
3EC4.1 |
Active keyboards | The app is a custom keyboard app | Providing a system-wide keyboard must be its primary function |
SDK wrapper reason codes such as C56D.1 and 0A2A.1 are only for third-party SDKs. Don’t use them in your app’s own manifest.
Most apps only need two or three entries. Use CA92.1 for remembering sign-in state and settings, C617.1 for reading file modification dates when clearing a cache, E174.1 for checking free space before a download, and 35F9.1 for measuring how long a screen stays open.
Find what your code uses
Search the source in the project root to see which APIs show up. On Windows, run this in PowerShell.
1Get-ChildItem -Recurse -Include *.swift,*.m,*.mm,*.c,*.cpp |
2 Select-String -Pattern 'UserDefaults|@AppStorage|creationDate|modificationDate|contentModificationDateKey|systemUptime|mach_absolute_time|volumeAvailableCapacity|volumeTotalCapacity|systemFreeSize|statfs|statvfs|activeInputModes|\bstat\(|\blstat\(|\bfstat\(' |
3 Select-Object Path, LineNumber, Line
On Linux or WSL, use grep.
1grep -rnE --include='*.swift' --include='*.m' --include='*.mm' --include='*.c' --include='*.cpp' \
2 'UserDefaults|@AppStorage|creationDate|modificationDate|contentModificationDateKey|systemUptime|mach_absolute_time|volumeAvailableCapacity|volumeTotalCapacity|systemFreeSize|statfs|statvfs|activeInputModes|\bstat\(|\blstat\(|\bfstat\(' .
Search only your own source folders. Third-party code in Pods, .build and node_modules is covered by each SDK’s own manifest. Add one dictionary to your manifest for each category that matches. If the email after upload lists more categories than you declared, the extra ones usually come from a third-party SDK.
What does a complete privacy manifest look like?
Here is a complete manifest for a typical app. It doesn’t track users, collects an email address for sign-in that is linked to the user, collects crash data for analytics, and uses UserDefaults, file timestamps, system boot time and disk space.
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
4<dict>
5 <key>NSPrivacyTracking</key>
6 <false/>
7 <key>NSPrivacyCollectedDataTypes</key>
8 <array>
9 <dict>
10 <key>NSPrivacyCollectedDataType</key>
11 <string>NSPrivacyCollectedDataTypeEmailAddress</string>
12 <key>NSPrivacyCollectedDataTypeLinked</key>
13 <true/>
14 <key>NSPrivacyCollectedDataTypeTracking</key>
15 <false/>
16 <key>NSPrivacyCollectedDataTypePurposes</key>
17 <array>
18 <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
19 </array>
20 </dict>
21 <dict>
22 <key>NSPrivacyCollectedDataType</key>
23 <string>NSPrivacyCollectedDataTypeCrashData</string>
24 <key>NSPrivacyCollectedDataTypeLinked</key>
25 <false/>
26 <key>NSPrivacyCollectedDataTypeTracking</key>
27 <false/>
28 <key>NSPrivacyCollectedDataTypePurposes</key>
29 <array>
30 <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
31 </array>
32 </dict>
33 </array>
34 <key>NSPrivacyAccessedAPITypes</key>
35 <array>
36 <dict>
37 <key>NSPrivacyAccessedAPIType</key>
38 <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
39 <key>NSPrivacyAccessedAPITypeReasons</key>
40 <array>
41 <string>CA92.1</string>
42 </array>
43 </dict>
44 <dict>
45 <key>NSPrivacyAccessedAPIType</key>
46 <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
47 <key>NSPrivacyAccessedAPITypeReasons</key>
48 <array>
49 <string>C617.1</string>
50 </array>
51 </dict>
52 <dict>
53 <key>NSPrivacyAccessedAPIType</key>
54 <string>NSPrivacyAccessedAPICategorySystemBootTime</string>
55 <key>NSPrivacyAccessedAPITypeReasons</key>
56 <array>
57 <string>35F9.1</string>
58 </array>
59 </dict>
60 <dict>
61 <key>NSPrivacyAccessedAPIType</key>
62 <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
63 <key>NSPrivacyAccessedAPITypeReasons</key>
64 <array>
65 <string>E174.1</string>
66 </array>
67 </dict>
68 </array>
69</dict>
70</plist>
Adjust it to your app.
- If you only use UserDefaults, keep only the first dictionary in
NSPrivacyAccessedAPITypes. - If you collect no data, set
NSPrivacyCollectedDataTypesto an empty array,<array/>. - When you stop using a category, delete its whole dictionary. Don’t leave a dictionary with no reason codes. If
NSPrivacyAccessedAPITypesends up with no entries, remove the key too. - If an advertising or attribution SDK tracks users, change
NSPrivacyTrackingto<true/>and addNSPrivacyTrackingDomainswith the tracking domains. The array can’t be empty.
Other common data type values are NSPrivacyCollectedDataTypeName, NSPrivacyCollectedDataTypePhoneNumber, NSPrivacyCollectedDataTypeUserID, NSPrivacyCollectedDataTypeDeviceID, NSPrivacyCollectedDataTypePreciseLocation and NSPrivacyCollectedDataTypeProductInteraction.
Other purposes are NSPrivacyCollectedDataTypePurposeThirdPartyAdvertising, NSPrivacyCollectedDataTypePurposeDeveloperAdvertising, NSPrivacyCollectedDataTypePurposeProductPersonalization and NSPrivacyCollectedDataTypePurposeOther. See Apple’s describing data use in privacy manifests for the full list.
How do you add the privacy manifest to the project?
The privacy manifest must be packaged as a resource of the app target, so that after the build it sits at the root of the .app.
Create the file
- In the VS Code Explorer, go to the app’s source folder (the one next to
Info.plistandAssets.xcassets) and create a file namedPrivacyInfo.xcprivacy. - Paste the XML from the previous section, adjust it to your app and save.
- For later edits, double-click the file to open it in the Property List Editor as a table. Dictionaries and arrays expand and collapse, right-click an array and choose Add Child to add a reason code, and pick the type in each row’s type column. Keep the save format as XML so diffs are easy to read in code review.
Add it to the app target
The new file only goes into the package if it belongs to the app target.
- If the project uses folder synchronization from Xcode 16 onward (shown as blue folders in Xcode), placing the file in the source folder already adds it. Nothing else to do.
- KXBuild templates and older projects use groups. Register the file once with the command below. See the kxbuild command reference for the options.
1kxbuild project add-resource -f MyApp/PrivacyInfo.xcprivacy -t MyApp -P .\MyApp.xcodeproj
If the project has several app targets, for example separate production and beta apps, run it for every target you submit. If an extension such as a widget uses required reason APIs, the extension bundle needs its own manifest too. Register another copy with -t set to the extension target.
Manifests in Swift packages
If you maintain a Swift package that should ship with a manifest, put the file under Sources/<TargetName>/ and declare it as a resource in Package.swift. Otherwise it isn’t treated as a resource.
1targets: [
2 .target(
3 name: "SomeLibrary",
4 resources: [
5 .process("PrivacyInfo.xcprivacy")
6 ]
7 ),
8]
Check the build
Build a Release package with KXBuild: Build in VS Code, or on the command line.
1kxbuild build .\MyApp.xcodeproj -c Release
An .ipa is a zip archive, with the app under Payload/<App>.app/. Windows 10 and later include tar, so you can list the privacy manifests in the package directly.
1tar -tf .\build\MyApp.ipa | findstr PrivacyInfo.xcprivacy
On Linux or WSL, use unzip.
1unzip -l build/MyApp.ipa | grep PrivacyInfo.xcprivacy
The output must include Payload/MyApp.app/PrivacyInfo.xcprivacy, which is the app’s own manifest. Other lines are usually manifests that ship with frameworks and resource bundles, for example Payload/MyApp.app/Frameworks/Xxx.framework/PrivacyInfo.xcprivacy.
To confirm the packaged file is the one you wrote, extract it and look.
1mkdir .\ipa-check
2tar -xf .\build\MyApp.ipa -C .\ipa-check
3code .\ipa-check\Payload\MyApp.app\PrivacyInfo.xcprivacy
If the app root line is missing, check these three things in order.
- The file name is exactly
PrivacyInfo.xcprivacy, with matching case and extension. - The file belongs to the app target. In a group-based project, run
kxbuild project add-resourceagain. - The file isn’t in a folder excluded from the build. Move it back to the app’s source folder.
How do you check third-party SDK manifests?
Which SDKs must include one
Apple maintains a list of commonly used third-party SDKs. Every version of a listed SDK must include a privacy manifest, and SDKs added as binaries must also be signed.
The list includes Alamofire, AFNetworking, the Firebase family, FBSDKCoreKit, GoogleSignIn, Kingfisher, SDWebImage, Lottie, SnapKit, RxSwift, Realm, OneSignal, Flutter, hermes, Capacitor, Cordova, UnityFramework, and Flutter plugins such as shared_preferences_ios, path_provider, url_launcher and image_picker_ios.
When a new app includes a listed SDK, or an update adds one, a missing manifest triggers ITMS-91061.
SDKs that aren’t on the list should still ship their own manifest if they use required reason APIs, collect user data or connect to tracking domains.
How to check
- List every
PrivacyInfo.xcprivacyin the package with thetar -tfcommand from the previous section. - List the frameworks in the package and compare them with the SDK list to see which have no manifest.
1tar -tf .\build\MyApp.ipa | findstr /R "\.framework/Info\.plist$"
- Upgrade the ones without a manifest, depending on how you add them. For CocoaPods, change the version in the
Podfileand runpod installagain. For Swift packages, update the version requirement and runkxbuild project resolve --forceto resolve again. For Flutter plugins, update the version inpubspec.yaml. For React Native and Expo, upgrade the npm package and regenerate the iOS project.
SDKs shipped as static libraries (.a) can’t carry resources. You have to wait for the vendor to switch to a static framework or XCFramework that includes a manifest. Apple recommends contacting the SDK vendor for an updated version instead of declaring the SDK’s privacy behavior in your own manifest.
How do you fix ITMS-91053 and ITMS-91056?
| Code | Meaning | Fix |
|---|---|---|
| ITMS-91053 | Missing required reason API declaration, with the API categories listed in the email | If your code uses it, add the category and reason code to the app manifest. If it comes from an SDK, upgrade the SDK |
| ITMS-91061 | A listed third-party SDK has no privacy manifest | Upgrade the SDK named in the email to a version with a manifest |
| ITMS-91056 | Invalid privacy manifest, with the file path in the email | Check key spelling, value types and that each reason code belongs to its category |
| ITMS-90683 | Info.plist is missing a usage description | This is an Info.plist problem, not the manifest. Add the matching NS…UsageDescription |
The most common ITMS-91056 mistakes, from TN3181, are these.
NSPrivacyTrackingisn’t a Boolean.NSPrivacyTrackingis true butNSPrivacyTrackingDomainsis an empty array.NSPrivacyTrackingis false butNSPrivacyTrackingDomainsis present.- The category name in
NSPrivacyAccessedAPITypeis misspelled, or a reason code doesn’t belong to the category, for exampleC617.1under UserDefaults. NSPrivacyAccessedAPITypeReasonsis an empty array or isn’t an array.
For other upload error codes, see the ITMS error code reference.
Privacy manifests and privacy nutrition labels
The App Privacy label on the App Store page, often called the privacy nutrition label, comes from the questionnaire you fill in on the App Privacy page in App Store Connect. It isn’t generated from the privacy manifest. Both describe the same thing, so their content must match.
| Privacy manifest | App Privacy questionnaire (nutrition label) | |
|---|---|---|
| Where | Packaged in the .ipa | Filled in on the App Store Connect website |
| Who writes it | The app and each SDK write their own | You fill in one for the whole app |
| Content | Data types, tracking, required reason APIs | Data types, purposes, linked to identity, tracking |
| Who sees it | Apple’s upload checks | App Store users |
When you fill in the questionnaire, combine NSPrivacyCollectedDataTypes from the app manifest and every SDK manifest, and map each entry to a data type and purpose in the questionnaire. Data collected by SDKs counts toward your app’s label. Required reason APIs only go in the manifest and have no counterpart in the questionnaire.
Pre-submission checklist
- The app’s source folder contains
PrivacyInfo.xcprivacywith the correct case. - Every required reason API category found in your source has a dictionary and reason code in the manifest, with no empty arrays.
NSPrivacyTrackingandNSPrivacyTrackingDomainsare consistent.- Every app target you submit has the manifest registered, and extensions that use these APIs have their own.
tar -tfshowsPayload/<App>.app/PrivacyInfo.xcprivacy.- Every listed third-party SDK is on a version that includes a manifest.
- The App Privacy questionnaire in App Store Connect matches the data collection in all manifests.
Once every item is checked, build with kxbuild build -c Release --export app-store and upload. With a complete iOS privacy manifest, ITMS-91053, ITMS-91056 and ITMS-91061 stop appearing.
FAQ
Can I submit to the App Store without PrivacyInfo.xcprivacy?
Not if your code uses required reason APIs such as UserDefaults. Since May 1, 2024, App Store Connect rejects apps that don’t declare reasons for these APIs. An app that truly uses none of them and collects no data can leave it out, but third-party SDKs still need their own manifests.
What does the privacy manifest need if I only use @AppStorage?
@AppStorage is built on UserDefaults. Add a dictionary to NSPrivacyAccessedAPITypes with the category NSPrivacyAccessedAPICategoryUserDefaults and the reason code CA92.1. If you share data with a widget through an App Group, add 1C8F.1 as well.
Do I declare a third-party SDK’s API usage in my own manifest?
No. Apple requires each SDK to declare its usage in its own manifest, and your app’s manifest can’t stand in for it. If you get ITMS-91053 or ITMS-91061 pointing at an SDK, upgrade that SDK to a version with a manifest.
How do I check on Windows that the manifest is in the .ipa?
Run tar -tf .\build\MyApp.ipa | findstr PrivacyInfo.xcprivacy in PowerShell. If the output includes Payload/MyApp.app/PrivacyInfo.xcprivacy, the manifest is at the app root. If not, check the file name and add it to the app target with kxbuild project add-resource.
What’s the difference between the privacy manifest and the App Privacy questionnaire?
The privacy manifest ships in the package for Apple’s checks, while the App Privacy questionnaire determines the privacy label users see on the App Store page. You fill in the questionnaire by hand, and it must match the data collection declared in the manifests of your app and all its SDKs.