An iOS privacy manifest is a PrivacyInfo.xcprivacy property list bundled inside your .NET MAUI app that declares which Required Reason APIs you call, what data your app collects, and whether you perform tracking. Apple has been rejecting App Store submissions without it since spring 2024, and as of 2026 the manifest also has to be signed for every third-party SDK on Apple's "commonly used" list. In this guide I'll walk through how we wire iOS Privacy Manifests in .NET MAUI 10 on the team I lead, including the gotchas around NSUserDefaults, file-timestamp APIs, and SDK signature validation that are quietly torpedoing release builds.
Apple requires a PrivacyInfo.xcprivacy file in every iOS app binary. Missing manifests trigger ITMS-91065 / ITMS-91053 rejection emails on upload to App Store Connect.
.NET MAUI 10 ships a base manifest in the iOS workload, but you almost always need to add your own at Platforms/iOS/PrivacyInfo.xcprivacy with build action BundleResource.
You must declare a "Required Reason" code for every category of API you touch. The five buckets are UserDefaults, file timestamps, system boot time, disk space, and active keyboards.
Third-party SDKs (Firebase, Sentry, AppCenter shims, ZXing.Net.Maui, etc.) must each ship a signed manifest. If any are missing, Xcode 16+ blocks archive uploads, not just the App Store.
For tracking SDKs, set NSPrivacyTracking to true and list every tracking domain. Domains not in the list are silently blocked at runtime on iOS 17.2+.
What is an iOS privacy manifest?
A privacy manifest is an XML property-list file named PrivacyInfo.xcprivacy that Apple introduced at WWDC 2023 and made mandatory for App Store submissions starting May 1, 2024. It has four top-level keys: NSPrivacyTracking (boolean), NSPrivacyTrackingDomains (array of strings), NSPrivacyCollectedDataTypes (array of dictionaries describing what user data you collect), and NSPrivacyAccessedAPITypes (array describing which Required Reason APIs you call and why). Apple stitches these manifests, yours plus every SDK's, into a unified privacy report that ships with the build.
In practice, the manifest is how Apple holds the entire dependency tree accountable. We had a release in late 2024 where a single transitive Microsoft.Maui.Essentials reference brought in a binary that read systemUptime without declaring the reason. App Store Connect rejected the archive within seconds of upload with no helpful diff. The manifest is the contract; nothing about your Info.plist, entitlements, or capabilities replaces it.
From a .NET MAUI 10 perspective, the framework itself ships a manifest as of the net10.0-ios workload covering the APIs the runtime uses. You're responsible for everything your code touches on top of that, and almost every non-trivial app touches at least NSUserDefaults via Preferences.Default.
How to add PrivacyInfo.xcprivacy to a .NET MAUI 10 project
So, the mechanics are simple, but the build-action choice trips up almost everyone the first time. Create the file at Platforms/iOS/PrivacyInfo.xcprivacy and mark it as a BundleResource with LogicalName set so it lands at the root of the .app bundle (not nested under Platforms/iOS/).
Here's the minimal manifest we use as a starting point for every MAUI app. Save this as Platforms/iOS/PrivacyInfo.xcprivacy:
Then wire the file into the build in your csproj. The two things that matter are the Condition (so Android builds skip it) and the LogicalName (so it ends up at the bundle root):
Apple defined five "Required Reason API" categories. If your code (or any binary you link) calls into any of them, you must declare both the category and a numeric reason code. For .NET MAUI apps, the four you'll hit in practice are:
NSPrivacyAccessedAPICategoryUserDefaults: anything that calls NSUserDefaults under the hood. Preferences.Default.Set(...) in MAUI Essentials and most "settings" libraries land here. Reason CA92.1 ("Access info from same app") is the safe default.
NSPrivacyAccessedAPICategoryFileTimestamp: File.GetLastWriteTime, FileInfo.CreationTime, NSFileManager attributesOfItemAtPath. Reason C617.1 covers timestamps you display to the user; DDA9.1 covers timestamps used for inside-the-app file management.
NSPrivacyAccessedAPICategorySystemBootTime: System.Environment.TickCount, Stopwatch on iOS, and several telemetry SDKs. Reason 35F9.1 for "measure time on device".
NSPrivacyAccessedAPICategoryDiskSpace: NSFileManager free/total volume queries. Reason E174.1 for "write to disk when device is low on storage".
Here's the full NSPrivacyAccessedAPITypes block we use for a typical line-of-business app that stores preferences, caches files, and writes a log:
The full list of reason codes lives in Apple's "Describing use of required reason API" documentation. Treat that page as authoritative. Third-party blog posts (including older versions of this one) routinely list codes that Apple has since renamed.
Tracking, NSPrivacyTracking, and tracking domains
If your app fingerprints users, ties identifiers across apps, or sends data used for ad targeting, you must set NSPrivacyTracking to true and list every domain that receives tracking traffic in NSPrivacyTrackingDomains. The teeth on this came in iOS 17.2: any domain not listed but that the system classifies as tracking is silently NSURLSession-blocked when the user hasn't granted ATT permission. We caught this the hard way when a marketing pixel disappeared and we couldn't figure out why our DAU graph diverged from analytics.
For a typical MAUI app that ships AppsFlyer plus a Firebase Analytics shim, the block looks like this:
If your app does not track users (and many B2B and enterprise MAUI apps genuinely don't), leave NSPrivacyTracking false and the array empty. Don't reflexively check the box. Apple's review team has been known to push back when tracking is declared but the privacy questionnaire in App Store Connect says otherwise.
Declaring NSPrivacyCollectedDataTypes
The NSPrivacyCollectedDataTypes array mirrors what you fill out in App Store Connect's "Privacy Practices" questionnaire, but inside the binary so SDKs can contribute too. Each entry describes one type of data your app collects, whether it's linked to the user, used for tracking, and the purposes it's used for. Apple publishes the type list in the data-use documentation; common ones for MAUI apps are NSPrivacyCollectedDataTypeEmailAddress, NSPrivacyCollectedDataTypeCrashData, and NSPrivacyCollectedDataTypeProductInteraction.
Two practical notes from our team. First, NSPrivacyCollectedDataTypeLinked means linked to a user identity, so if you crash-report with a UUID that you never tie back to an account, that's not linked. Second, the purposes are AND-ed at review time with your App Store questionnaire. If you say "App Functionality" here but tick "Analytics" in App Store Connect, expect a rejection asking you to reconcile them. We learned that the hard way during a release we'd already promised to stakeholders (not fun). If you also store auth tokens, our guide on securing .NET MAUI apps with authentication and token management walks through the keychain choices that keep credential data out of the "linked" bucket.
Third-party SDK manifests and signature validation
Since Xcode 15.3, Apple maintains a list of "commonly used SDKs" (currently around 90 packages including Firebase, Adjust, AppsFlyer, Sentry, Adobe Analytics, OneSignal, RevenueCat, and Branch) that must ship both a privacy manifest and a cryptographic signature inside their xcframework. If any one of them is missing, xcodebuild archive fails locally with error ITMS-91065 on upload. For .NET MAUI 10 this matters because most of these SDKs reach you via NuGet binding packages (e.g., Plugin.Firebase, Sentry.Maui) that may lag the upstream xcframework by weeks.
Our checklist when a release is blocked on an SDK:
Open the .app bundle (build with dotnet build -t:Run -f net10.0-ios and then poke into bin/Debug/net10.0-ios/iossimulator-arm64/<app>.app/Frameworks/).
For every .framework there, check for a PrivacyInfo.xcprivacy at the framework root and a _CodeSignature directory with a signature.
If a framework is missing the manifest, check the NuGet package's GitHub releases page for a newer version. Most maintainers shipped fixed builds during late 2024.
If no fixed version exists, you can add the manifest manually with a build target that copies PrivacyInfo.xcprivacy into the framework directory before signing, but this is a stop-gap; signature validation still fails.
Fixing ITMS-91065 and ITMS-91053 rejection emails
Honestly, two error codes account for almost every privacy-manifest rejection email I've seen.
ITMS-91065, Missing signature. One of the "commonly used" SDKs you embedded didn't ship a code signature. The email names the framework. Update or replace the offending package; in the worst case, fork the binding and rebuild against a newer xcframework. Stripping the framework out isn't an option if it's a hard dependency.
ITMS-91053, Missing API declaration. Your manifest didn't declare a required-reason API that the static analyzer detected in your binary. The email names both the category and the symbol. Add the relevant entry to NSPrivacyAccessedAPITypes. A subtle case is when MAUI's FileSystem.AppDataDirectory indirectly calls into NSFileManager attributesOfItemAtPath: on the first launch. If you never thought you used file timestamps but the rejection says you did, that's usually the culprit. (I hit this exact bug shipping a stock-counting app in late 2025; took me half a day to track down.)
Submission failures show up in your email and in App Store Connect's "Activity" tab within about 90 seconds of upload completion. Don't wait for review; the rejection is automated and pre-review. While you're hardening the release pipeline, our guide to .NET MAUI CI/CD with GitHub Actions and Azure DevOps shows how to fail the build locally if the manifest is missing, which has saved us several broken release cycles.
How to verify your privacy manifest locally before upload
The fastest local check is to archive the app and have Xcode generate the privacy report. Open the archive in the Organizer, right-click, and choose "Generate Privacy Report". It produces a PDF that aggregates every manifest in the build. If a manifest is missing or malformed, it shows up there before Apple ever sees it.
For automation, run plutil -lint Platforms/iOS/PrivacyInfo.xcprivacy in CI to catch syntax errors, and use Apple's privacy-manifest-tool (shipped with Xcode 16) for semantic validation:
# Lint syntax
plutil -lint Platforms/iOS/PrivacyInfo.xcprivacy
# Build and inspect what landed in the .app
dotnet publish -f net10.0-ios -c Release -p:RuntimeIdentifier=ios-arm64
find bin/Release/net10.0-ios -name "PrivacyInfo.xcprivacy"
# Aggregate report (Xcode 16+)
xcrun privacy-manifest-tool aggregate \
--input-dir bin/Release/net10.0-ios/ios-arm64/MyApp.app \
--output privacy-report.json
We've added the plutil -lint step plus the find check as required CI gates. If the manifest doesn't show up in the .app output, the pipeline fails before anyone wastes a build minute on the upload. Combined with the iOS 18 SafeArea fixes we've written about, those two gates have caught more would-be rejections than every code review in 2025 combined.
Frequently Asked Questions
Do I need a privacy manifest if my .NET MAUI app is iOS-only and doesn't track users?
Yes. Apple requires the PrivacyInfo.xcprivacy file in every iOS app regardless of whether you track users. Set NSPrivacyTracking to false and leave the tracking domains array empty, but you still need to declare any Required Reason APIs you call, and Preferences.Default alone is enough to require a UserDefaults entry.
Why does Apple reject my MAUI app with ITMS-91065?
ITMS-91065 means one of the third-party SDKs on Apple's "commonly used" list is missing a code signature inside its xcframework. The rejection email names the offending framework. Update the NuGet binding package to a version published after late 2024, or rebuild the binding against a newer signed xcframework.
Where do I put PrivacyInfo.xcprivacy in a .NET MAUI project?
Place it at Platforms/iOS/PrivacyInfo.xcprivacy and add a BundleResource entry in your csproj with LogicalName="PrivacyInfo.xcprivacy" so it lands at the root of the .app bundle. Use a TargetFramework.Contains('-ios') condition so Android builds skip it.
What are Required Reason APIs in iOS?
Required Reason APIs are five categories of iOS APIs (UserDefaults, file timestamps, system boot time, disk space, and active keyboard list) that Apple considers high-risk for fingerprinting. If your app or any linked binary calls them, you must declare both the category and a numeric reason code in your privacy manifest.
How can I tell which tracking domains my MAUI app uses?
Archive the app in Xcode and generate a Privacy Report from the Organizer. Apple aggregates every domain declared by every linked SDK into a single PDF. You can also inspect each SDK's own PrivacyInfo.xcprivacy inside its .framework directory in the .app bundle.
Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.
A walkthrough of why .NET MAUI apps show black bars on iOS 18 and 26 edge-to-edge devices, what changed in Apple's safe-area defaults, and the exact SafeAreaEdges fix I shipped to production.
A step-by-step walkthrough of the duplicate class androidx error that surfaces after upgrading MAUI from .NET 9 to .NET 10, with diagnosis commands, real fixes, and the version pins that actually held up in production.
If you upgraded a .NET MAUI app to .NET 10 and then ran it on a device or emulator with Android 16 (API 36), you may have noticed something ugly: the status bar now sits on top of your Shell header.