Blogs

Fix ITMS-91061 in Flutter: Find the SDK Missing PrivacyInfo.xcprivacy

Last updated: September 27, 2026

An ITMS-91061 email does not mean that every privacy declaration belongs in Runner/PrivacyInfo.xcprivacy. In a Flutter app, the path in Apple's message often points to Flutter.framework or to a framework delivered by a plugin. That distinction determines who must supply the missing manifest.

This guide uses a conservative rule: diagnose the bundle named by Apple, update or replace the component that owns that bundle, and verify the built archive. Do not copy a third-party SDK's declarations into the main app manifest merely to make the email disappear.

Quick answer: for ITMS-91061, start with the SDK name and framework path in the rejection email. Upgrade the Flutter SDK or plugin that owns that framework. The third-party SDK should ship its own valid PrivacyInfo.xcprivacy file.

First separate ITMS-91061 from the other privacy errors

These errors can arrive together, but they do not have the same fix.

ErrorWhat Apple is reportingFirst place to investigate
ITMS-91053A Required Reason API category was detected without an accepted reason declarationYour app code and the component using the API
ITMS-91056A PrivacyInfo.xcprivacy file is malformed or contains invalid valuesThe exact manifest path named in the email
ITMS-91061A listed third-party SDK does not include its required privacy manifestThe SDK or plugin that owns the named framework

Apple's third-party SDK requirements list Flutter and many common Flutter plugins. The list currently includes packages and native dependencies such as connectivity_plus, file_picker, FirebaseCore, flutter_local_notifications, path_provider, shared_preferences_ios, and webview_flutter_wkwebview.

If the email contains more than one SDK, treat each SDK as a separate finding. Fixing the first framework does not repair the others.

Step 1: preserve the evidence from Apple's email

Copy these fields into the issue or release ticket before changing dependencies:

  • Error code.
  • Framework or bundle path.
  • SDK name.
  • Required Reason API category, if Apple provides one.
  • The build number that triggered the message.

For example, a path such as:

Frameworks/flutter_local_notifications.framework/flutter_local_notifications

points to the plugin's native framework, not to the Runner target. A path containing Frameworks/Flutter.framework/Flutter points to the Flutter engine framework. Do not discard the path; it is the strongest ownership signal in the message.

You can paste the complete email into the App Store Privacy Error Checker. The parser runs in the browser and extracts the error code, SDK names, paths, and API categories without uploading the message.

Step 2: inventory the Flutter and iOS dependency versions

Record the toolchain before upgrading anything:

flutter --version
flutter doctor -v
flutter pub outdated

Then inspect the resolved native dependencies:

grep -n "PODS:" ios/Podfile.lock
grep -n "flutter_local_notifications\|FirebaseCore\|GoogleUtilities" ios/Podfile.lock

Replace the example names with the SDK named by Apple. The Dart package name and the final CocoaPod or framework name are not always identical, so use pubspec.lock, ios/Podfile.lock, and the email path together.

Flutter added an iOS framework privacy manifest in the Flutter 3.19 release line. That is documented in the official Flutter 3.19 release notes. This does not mean every plugin in an older project is covered. Plugins and repackaged SDKs remain responsible for their own native bundles.

Step 3: find the manifests that exist before the build

Search the source checkout and package cache:

find ios .dart_tool -name PrivacyInfo.xcprivacy -print 2>/dev/null
find "$HOME/.pub-cache" -path "*your_plugin*" -name PrivacyInfo.xcprivacy -print 2>/dev/null

Finding a file in a package directory is useful, but it is not proof that the file reaches the archived app. A podspec or Swift package resource rule still has to copy it into the correct bundle.

If you maintain the Flutter plugin yourself, the official Flutter Swift Package Manager guidance shows how a plugin target processes PrivacyInfo.xcprivacy as a resource. CocoaPods-based plugins commonly expose the file through a resource bundle in the podspec. In either case, the manifest must describe that plugin's behavior, not the host app's unrelated behavior.

Step 4: choose the fix based on ownership

When the email names Flutter.framework

Upgrade to a supported Flutter stable release and rebuild from a clean dependency state. Confirm the release you choose includes the engine manifest and is compatible with your project.

flutter upgrade
flutter clean
flutter pub get
cd ios
pod install --repo-update
cd ..

Review the generated lockfile changes instead of accepting an unbounded dependency jump. If the app has a release branch, make the upgrade there and run the app's regression suite before uploading.

When the email names a Flutter plugin or transitive SDK

Use this order:

  1. Upgrade the direct Dart package that brings in the SDK.
  2. Check whether the resolved pod version changed in Podfile.lock.
  3. Read the plugin or SDK release notes for privacy-manifest support.
  4. If no maintained version supplies a manifest, replace, remove, or fork the package.
  5. If you own the fork, add a manifest to the plugin or framework bundle and document the reasons truthfully.

Do not treat a copied file as harmless boilerplate. Each reason code is a declaration about how the code uses an API. The approved reason must match the actual behavior.

When your own Runner code triggers ITMS-91053

This is the case where an app-level declaration may be appropriate. Add only the category and approved reason that match your own code. For example, CA92.1 is commonly associated with app-specific UserDefaults access, but it is not a universal Flutter answer.

<key>NSPrivacyAccessedAPITypes</key>
<array>
  <dict>
    <key>NSPrivacyAccessedAPIType</key>
    <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
    <key>NSPrivacyAccessedAPITypeReasons</key>
    <array>
      <string>CA92.1</string>
    </array>
  </dict>
</array>

Before using a reason, read Apple's current Required Reason API documentation and confirm that your behavior satisfies its conditions. The Required Reason API Checker can catch category-and-code mismatches, but it cannot determine your product intent.

Step 5: validate the manifest as a property list

Run Apple's property-list parser on every manifest you changed:

plutil -lint ios/Runner/PrivacyInfo.xcprivacy
plutil -p ios/Runner/PrivacyInfo.xcprivacy

For manifests inside packages, substitute the actual path. Then use the PrivacyInfo.xcprivacy Validator to check the supported top-level keys, expected types, empty values, tracking-domain formatting, and known Required Reason API mappings.

Common ITMS-91056 causes include:

  • The root object is not a dictionary.
  • A key that expects an array contains a string or dictionary.
  • A reason list contains an empty string.
  • A tracking domain includes https://, a path, a query string, or a trailing slash.
  • A manifest contains a category with a reason code approved for a different category.
  • A framework contains a malformed manifest even though the Runner manifest is valid.

Apple's TN3181 debugging note is the primary reference for tracing an invalid manifest.

Step 6: inspect the archive, not just the repository

Create the same release artifact you intend to upload:

flutter build ipa --release

Then inventory every manifest in the archive:

find build/ios/archive -name PrivacyInfo.xcprivacy -print

Inspect the framework named in Apple's email. A healthy result should show the SDK's manifest inside the SDK's bundle or resource bundle, depending on how the vendor packages it. The Runner manifest alone is not evidence that a listed third-party SDK satisfies ITMS-91061.

You can also validate every archived manifest:

find build/ios/archive -name PrivacyInfo.xcprivacy -print0 \
  | xargs -0 -n1 plutil -lint

If the source package contains a manifest but the archive does not, investigate the podspec, Swift package resources, Xcode Copy Bundle Resources phase, or custom build scripts. That is a packaging failure, not a reason-code problem.

Fixes that create false confidence

Avoid these shortcuts:

  • Adding every reason code found on the internet.
  • Copying all plugin declarations into Runner/PrivacyInfo.xcprivacy to address ITMS-91061.
  • Checking only node_modules or .pub-cache and never checking the archive.
  • Renaming an arbitrary plist to PrivacyInfo.xcprivacy without validating its schema.
  • Declaring tracking as false without reviewing the SDK's actual behavior.
  • Assuming a successful local build proves App Store manifest compliance.

For ITMS-91061 specifically, Apple's guidance is clear that the app manifest should describe the app and that a privacy-impacting third-party SDK should provide its own manifest. See the detailed ITMS-91061 explanation before changing the Runner target.

Final Flutter submission checklist

  • The exact SDK and path from every Apple message are recorded.
  • Flutter and affected plugin versions are known.
  • The responsible SDK version contains its own manifest.
  • Every changed manifest passes plutil -lint.
  • Category and reason codes match current Apple documentation.
  • The release archive contains manifests in the expected bundles.
  • No third-party SDK declaration was blindly moved into the Runner manifest.
  • The result is reviewed by a developer who understands the actual API use.

This workflow reduces guesswork, but it cannot guarantee App Review approval and is not legal advice. Apple's rules, SDK packaging, and the behavior of your app remain the source of truth.

Paste the rejection email and inspect the manifest locally before spending another build-and-upload cycle.

Check My Privacy Manifest