In a Flutter iOS app, what is PrivacyInfo.xcprivacy, and which parts of the app are expected to ship one?
answer
- a plist about data and APIs
- four top-level NSPrivacy keys
- required-reason API categories and codes
- Flutter.framework carries its own
- plugin template includes one
basics
~10 sPrivacyInfo.xcprivacy is Apple's privacy manifest, a plist declaring tracking, collected data types and required-reason API use; Flutter's engine framework ships one, plugins ship their own, and the app adds one for its own code.
solid answer
~40 s`PrivacyInfo.xcprivacy` is Apple's **privacy manifest**, a property list with four top-level keys: `NSPrivacyTracking`, `NSPrivacyTrackingDomains`, `NSPrivacyCollectedDataTypes` and `NSPrivacyAccessedAPITypes`, the last listing each **required-reason API** category with its reason codes. Every binary brings its own. Flutter's engine framework has shipped one since 3.19, declaring the file-timestamp and system-boot-time categories the engine uses. Plugins carry theirs, and `flutter create --template=plugin` has generated one since 3.24, bundled through the podspec's `resource_bundles` or the Swift package. The app adds one to the Runner target for its own native code and data collection. It is separate from `Info.plist` purpose strings and from the App Privacy answers in App Store Connect.
code
xml · 23 lines<?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/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
</dict>
</plist>go deeper
Recall that PrivacyInfo.xcprivacy is a plist about tracking, collected data and required-reason APIs, separate from Info.plist purpose strings.
Explain the four keys, that the engine, each plugin and the app ship separate manifests, and what the plugin template generates.
Audit a release: find plugins without manifests, declare the app's own native API use, and keep tracking declarations consistent with bundled SDKs.
Decide how native dependencies are vetted for privacy declarations and who keeps manifests and store privacy answers aligned across releases.
## What the privacy manifest is A **privacy manifest** is a property-list file named `PrivacyInfo.xcprivacy` that a bundle ships to describe its privacy behaviour in machine-readable form. Apple introduced it so the privacy impact of an app and of every SDK inside it can be collected in one place. It answers three questions: 1. Does this code **track** users, and to which domains does it connect for tracking? 2. Which **data types** does it collect, and why? 3. Which **required-reason APIs** does it call, and for which approved reason? **Required-reason APIs** are system APIs that could be used to fingerprint a device, grouped into categories such as file timestamps, system boot time, disk space and user defaults. Using one requires declaring an approved reason code. ## The four keys | Key | Type | Meaning | |---|---|---| | `NSPrivacyTracking` | Boolean | whether this code tracks users | | `NSPrivacyTrackingDomains` | array | domains contacted for tracking | | `NSPrivacyCollectedDataTypes` | array | collected data types and purposes | | `NSPrivacyAccessedAPITypes` | array of dicts | each `NSPrivacyAccessedAPIType` category with its `NSPrivacyAccessedAPITypeReasons` codes | The Flutter plugin template writes all four with empty arrays and tracking set to false, a neutral starting point the author must fill in. ## Who ships one in a Flutter app A Flutter iOS app is several binaries, and each is responsible for its own manifest: - **The Flutter engine framework.** Since Flutter 3.19 `Flutter.framework` includes a manifest. It declares `NSPrivacyAccessedAPICategoryFileTimestamp` with reasons `0A2A.1` and `C617.1`, and `NSPrivacyAccessedAPICategorySystemBootTime` with `35F9.1`, the APIs the engine itself uses. - **Plugins.** A plugin with iOS code that calls a required-reason API or collects data needs its own manifest. Since Flutter 3.24 the iOS plugin template generates `PrivacyInfo.xcprivacy` (macOS followed in 3.27). With CocoaPods the podspec exposes it through `resource_bundles`; a Swift package template places it in the package's sources. - **The app.** The Runner target carries a manifest for code the app team wrote in Swift or Objective-C and for the data the app collects, created in Xcode as an App Privacy file and added to Runner. What stays with the engine's manifest is only what the engine declares: an app's own native code, and every third-party SDK, answers for itself. ## How it differs from nearby things - **Purpose strings** (`NS…UsageDescription` in `Info.plist`) supply the text of permission prompts. The manifest does not replace them. - **App Privacy answers** in App Store Connect are what the store shows users. The manifests are an input a team uses to fill those answers honestly, not a substitute for them. - **Entitlements** grant capabilities through the provisioning profile; the manifest grants nothing. ## Practical checks before a release 1. List the iOS plugins in `pubspec.lock` and check that each one that touches native APIs ships a manifest; an outdated plugin may predate the template change. 2. Check the app's own Swift code, such as a platform-channel handler reading file dates or user defaults, and declare those categories in the Runner manifest. 3. Keep `NSPrivacyTracking` and the tracking domains consistent with any analytics or ads SDK the app bundles. ## Where the manifests end up When Xcode archives the app, each framework and resource bundle keeps its manifest inside the built product: the engine's inside `Flutter.framework`, each plugin's inside its framework or resource bundle, and the app's at the top of `Runner.app`. Nothing merges them into one file; they are read per bundle. That is why a manifest added to the wrong target, for example a plugin's usage declared only in Runner, does not describe the binary that actually makes the call. For a team this suggests a simple ownership rule: - the Flutter SDK upgrade brings the engine's manifest; - each plugin release is responsible for its own, and the team checks it when upgrading; - the app team owns the Runner manifest and updates it in the same change as any native code that touches a required-reason API. ## Common misconceptions - Dart code is not exempt from privacy rules; the engine declares the APIs it uses, and native code the app writes is the app's responsibility. - The manifest does not ask the user for anything; there is no prompt tied to it. - One manifest in the app does not cover every plugin; each bundle declares its own.
- A plugin your app depends on predates privacy manifests and its iOS code reads file timestamps; what do you do?Upgrade to a release that ships `PrivacyInfo.xcprivacy`, or send the maintainer a change adding one through the podspec's `resource_bundles` or the Swift package. Declaring the plugin's usage in your app's manifest papers over the gap in the wrong bundle.
- Does the privacy manifest replace the App Privacy answers in App Store Connect?No. Those answers are entered in App Store Connect and shown on the product page. Manifests from the app and its SDKs help a team answer them accurately; they do not fill them in.
saying these in an interview costs you the question
- Flutter apps are pure Dart, so no required-reason API is ever reached.
- One manifest in the Runner target covers every plugin in the app.
- PrivacyInfo.xcprivacy is where permission prompt text now lives.
- The privacy manifest shows the user a consent prompt at launch.
- Filling in the manifest also publishes the App Store privacy label.