Since Spring 2024, Apple requires every app — and every third-party SDK embedded in it — to include a PrivacyInfo.xcprivacy file. Miss it, and your App Store submission fails with a vague email about "missing privacy manifest." Get it wrong, and your app's privacy nutrition label silently declares data collection you never intended.
We maintain 27 apps across iOS, macOS, watchOS, and visionOS. Every one of them includes a privacy manifest. This guide covers what the file actually does, which entries you almost certainly need, how to audit your third-party SDKs, and the exact XML template you can copy into your project today.
What PrivacyInfo.xcprivacy Actually Is
The privacy manifest is a property list file that declares two things: what data your app collects, and which Apple-designated "required reason" APIs your code calls. It lives at the root of your app target (or inside each SDK's bundle) and is evaluated at App Store review time.
The file has exactly four top-level keys:
- NSPrivacyTracking — Boolean. Does your app use data for tracking as defined by Apple's App Tracking Transparency framework? For most indie apps without ad SDKs, this is
false. - NSPrivacyTrackingDomains — Array of domains contacted for tracking purposes. Empty array if
NSPrivacyTrackingis false. - NSPrivacyCollectedDataTypes — Array of dictionaries declaring what user data you collect (crash logs, analytics, purchase history). Each entry specifies whether data is linked to identity, used for tracking, and the collection purpose.
- NSPrivacyAccessedAPITypes — Array of dictionaries declaring which "required reason" APIs your code uses (UserDefaults, file timestamps, disk space, system boot time). This is where most indie developers get tripped up.
The Entry You Almost Certainly Need: UserDefaults (CA92.1)
If your app calls UserDefaults.standard — and virtually every iOS app does — you must declare it with reason code CA92.1. This is the single most commonly missed entry. Apple flags it during review, and the rejection message does not clearly explain what is wrong.
Here is what the entry looks like in XML:
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
Reason code CA92.1 means "access user defaults to read and write data accessible only to the app itself." This covers @AppStorage, UserDefaults.standard, and any SwiftUI property wrapper that persists to user defaults. Unless you are reading another app's defaults via an app group (which requires a different reason code), CA92.1 is what you need.
Other Common Required Reason APIs
Beyond UserDefaults, Apple designates four other API categories that require a declared reason. You may not use all of them, but most apps use at least two.
| API Category | Common Triggers | Reason Code |
|---|---|---|
| File Timestamp | NSFileCreationDate, stat, fstat |
C617.1 (app container) or DDA9.1 (display to user) |
| System Boot Time | ProcessInfo.systemUptime, mach_absolute_time |
35F9.1 (measure elapsed time) |
| Disk Space | volumeAvailableCapacityKey, statfs |
85F4.1 (check space before write) |
| Active Keyboards | UITextInputMode.activeInputModes |
3EC4.1 (customize UI per language) |
File Timestamp is the second most common entry. If your app reads when a file was created or modified — which includes any file manager, document picker, or import/export flow — you need C617.1 (accessing files in your own app container) or DDA9.1 (showing the date to the user). Our email conversion apps (Email Converter, Mbox Splitter Pro) both declare DDA9.1 because they display email dates in the UI.
System Boot Time catches people who use timing functions for animations or performance measurement. The mach_absolute_time() call is buried inside many system frameworks and third-party SDKs. If you use Sentry for crash reporting, it calls this API internally for performance tracing.
Disk Space applies if your app checks available storage before writing a large file. Common in photo/video apps, backup tools, and any app with an offline cache.
The Full XML Template
Here is the complete PrivacyInfo.xcprivacy file we use as a starting point for every new app. It declares no tracking, no collected data types (which you add per-app based on your actual data collection), and the two most common required reason APIs:
<?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>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<!-- Add entries here per app -->
<!-- Example: crash logs for Sentry -->
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeCrashData</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<!-- UserDefaults -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<!-- File Timestamp -->
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
Copy this file into your app target's root directory. Xcode will automatically include it in the app bundle. Then customize: remove any API categories you do not use, and add collected data type entries based on what your specific app actually collects.
Auditing Third-Party SDKs
Your app's privacy manifest covers your code. But every third-party SDK on Apple's list of "commonly used SDKs" must include its own PrivacyInfo.xcprivacy inside its framework bundle. Your manifest does not cover them — each SDK is responsible for its own declarations.
To audit what your entire app bundle actually declares, use Xcode's built-in tool:
Product > Generate Privacy Report
This generates a PDF listing every privacy manifest found in your app bundle — your app's own manifest plus every embedded SDK. Review it before submission. If an SDK is missing its manifest, you will see it here as an absence rather than discovering it through a rejection email.
The three SDKs we embed across all 27 apps through our shared Swift Package each handle their manifests differently:
- RevenueCat — Bundles its own
PrivacyInfo.xcprivacysince SDK v4.25. Declares purchase history data (not linked to identity, not used for tracking). If you are on an older version, update before submitting. - Sentry — Bundles its manifest since SDK v8.20. Declares crash data and performance data (diagnostic category). Also declares SystemBootTime API usage for its performance tracing. See our Sentry vs Crashlytics comparison for the full setup.
- TelemetryDeck — Bundles its manifest since SDK v2.0. Declares no collected data types because it uses differential privacy and does not collect personally identifiable information. This is a significant advantage over Firebase Analytics, which declares multiple data categories.
The key principle: you do not need to declare SDK data collection in your own manifest. Each SDK declares its own. But you do need to verify that every SDK actually includes its manifest. The Generate Privacy Report tool is the fastest way to verify.
Common Mistakes That Cause Rejections
After submitting 27 apps (many with multiple versions), these are the patterns that cause privacy manifest rejections:
- Missing UserDefaults declaration. The most common. Every app using
@AppStorage,UserDefaults.standard, or any SwiftUI persistence needs CA92.1. Apple's rejection email says "ITMS-91053" but does not specify which API you missed. - Outdated SDK without a manifest. Older versions of RevenueCat, Sentry, and dozens of other SDKs shipped before manifests were required. Run
swift package updateand regenerate with XcodeGen before submission. - Wrong reason code. Using C617.1 (app container access) when you should use DDA9.1 (display to user) will not cause an immediate rejection, but it misrepresents your API usage. Apple may flag it in future reviews.
- Declaring data you do not collect. Some developers copy a "complete" manifest template that declares analytics, advertising, and user identifiers. Your privacy nutrition label in the App Store is generated from this file. Over-declaring creates a worse label than necessary and may confuse users who see "Data Used to Track You" on a simple utility app.
- Forgetting the manifest on macOS targets. Privacy manifests apply to macOS apps submitted to the Mac App Store, not just iOS. If you build for both platforms with multi-platform XcodeGen targets, the manifest must be included in both.
Validation: How to Verify Before Submission
Three checks before every App Store submission:
- Generate Privacy Report (Product > Generate Privacy Report in Xcode). Confirm your app and all SDKs have manifests. Look for any SDK listed without a corresponding privacy report entry.
- Review the nutrition label preview. In App Store Connect, after uploading a build, the Privacy section shows a preview of what your app's nutrition label will look like. Verify it matches your expectations.
- Grep your codebase for required reason APIs. Search for
UserDefaults,FileManagerdate attributes,ProcessInfo.systemUptime,mach_absolute_time, andvolumeAvailableCapacityKey. If you find a call, you need the corresponding manifest entry.
# Quick check for required reason API usage grep -rn "UserDefaults\|@AppStorage" Sources/ grep -rn "creationDate\|modificationDate\|NSFileCreation" Sources/ grep -rn "systemUptime\|mach_absolute_time" Sources/ grep -rn "volumeAvailableCapacity\|statfs" Sources/
The Collected Data Types Section
The NSPrivacyCollectedDataTypes array is where you declare what user data your app actually collects — not just which APIs you call. Each entry requires four fields: the data type, whether it is linked to identity, whether it is used for tracking, and the purpose.
For our stack (TelemetryDeck + Sentry + RevenueCat), the collected data declaration is minimal:
- Crash data (from Sentry) — not linked to identity, not tracking, purpose: app functionality.
- Performance data (from Sentry) — not linked to identity, not tracking, purpose: app functionality.
- Purchase history (from RevenueCat) — not linked to identity, not tracking, purpose: app functionality.
TelemetryDeck declares nothing because its differential privacy approach means no personally identifiable data is collected. This is one of the reasons we chose it over Firebase Analytics — see the full comparison.
Key Takeaways
- Every app needs a privacy manifest. If you submit to the App Store without one, you will get a rejection. The file has four keys and takes ten minutes to create correctly.
- UserDefaults CA92.1 is almost always required. If your app uses
@AppStorageorUserDefaults.standard, declare it. This is the single most common rejection cause related to privacy manifests. - Your manifest covers your code; SDK manifests cover SDKs. Do not try to declare your SDK's data collection in your own manifest. Update SDKs to versions that include their own
PrivacyInfo.xcprivacy. - Generate Privacy Report before every submission. Product > Generate Privacy Report in Xcode shows exactly what your app bundle declares. Review it. It takes 30 seconds and prevents a multi-day rejection cycle.
- Declare only what you actually collect. Over-declaring makes your nutrition label worse for no reason. Start with the minimal template and add entries only for data your app genuinely collects.