DEV Community

Ship Desk
Ship Desk

Posted on

Fixing ITMS-91053 "Missing API declaration" in a Capacitor/Flutter app: a plain guide to PrivacyInfo.xcprivacy

If you shipped an app built with Capacitor or Flutter and uploaded a build to App Store Connect, you may have gotten an email that starts like this:

ITMS-91053: Missing API declaration - Your app's code in the "App" file references one or more APIs that require reasons, including the following API categories: NSPrivacyAccessedAPICategoryUserDefaults.

It looks alarming, especially the first time. It usually isn't a rejection. It's Apple telling you that your build, or one of the native frameworks bundled inside it, calls a system API that Apple classifies as "required reason," and no privacy manifest in the build declares an approved reason for that call. Apple has required these declarations for new uploads since May 2024, and as of 2026 it applies to Capacitor, Flutter, React Native and any other wrapped app the same way it applies to a fully native one.

This post is a plain walkthrough of what the message means, how to read which file Apple flagged, how to pick a reason code honestly, and where the resulting file goes in a Capacitor or Flutter project. I work on rejection triage for wrapped apps, so this is written from what actually shows up in those emails, not from a general reading of Apple's docs.

What the email is actually telling you

Read the message closely. It names two things: an API category (for example NSPrivacyAccessedAPICategoryUserDefaults, NSPrivacyAccessedAPICategoryFileTimestamp, NSPrivacyAccessedAPICategorySystemBootTime, or NSPrivacyAccessedAPICategoryDiskSpace) and a file where the code referencing that API lives, quoted as "code in the '...' file references."

That file name matters more than people expect. It's usually one of two things:

  • Your app's own binary ("App"). This includes any code statically linked into your app target, including plugin code that got compiled directly in.
  • A framework bundled separately, shown as a path like Frameworks/Capacitor.framework/Capacitor or Frameworks/SomePlugin.framework/SomePlugin.

The distinction decides who has to fix it. If Apple points at your app file, the fix belongs in your app's own PrivacyInfo.xcprivacy. If Apple points at a framework, that framework needs its own manifest, and your app's file does not cover it, no matter what you put in it. In that second case, the real fix is usually: update the plugin to a version that ships its own manifest.

For Capacitor apps this often means updating the core: current Capacitor versions (4.8.2, 5.7.4, 6.0.0 and 7.0.0 and later by major version, per Capacitor's own documentation) include their own manifest for the framework's known API use. Running npx cap sync ios after updating pulls that in. If the flagged framework name matches a Capacitor plugin package rather than the core, update that specific @capacitor/* package.

For Flutter, the flagged framework is usually the Flutter engine itself or a Dart plugin compiled into a native framework. Names ending in _foundation, _ios, _darwin, _plus, or sqflite-style packages are common. The fix is flutter pub upgrade on the specific plugin, followed by pod install in ios/.

One honest caveat: not every plugin has caught up. If no updated version exists that ships a manifest, you either wait, ask the maintainer, or replace the dependency. There isn't a way to declare on a third-party framework's behalf.

When it's your own app's job

If the flagged file is your app binary, you need a PrivacyInfo.xcprivacy in your own app target, and for each category Apple named, at least one reason code that actually matches what your code does.

The most common category in wrapped apps is user defaults (NSPrivacyAccessedAPICategoryUserDefaults), usually triggered by @capacitor/preferences, Flutter's shared_preferences, or similar storage plugins. If your app only reads and writes its own settings, the fitting reason is CA92.1, "read and write data that only this app can access." That's the common case. It is not correct if you're sharing data with an app extension or widget through an App Group; that's 1C8F.1 instead. And it does not cover reading data other apps or the system wrote, or writing data other apps can read; there's no code for that, because the API doesn't support it.

File timestamps (NSPrivacyAccessedAPICategoryFileTimestamp) show up from filesystem and cache plugins: @capacitor/filesystem, path_provider, sqflite, image caching libraries. If the code only reads metadata of files inside your app's own container, that's C617.1. If users pick files through a document picker, add 3B52.1 for that separately; it's a different reason for a different access pattern, and picking one doesn't cover the other.

System boot time (NSPrivacyAccessedAPICategorySystemBootTime) usually comes from timers, animations, or analytics SDKs measuring elapsed durations. 35F9.1 covers measuring time between in-app events; 8FFB.1 covers computing absolute event timestamps from boot time. These are close but distinct: the difference is whether boot time itself ever leaves the device (it must not, under either) and what exactly you're calculating.

Disk space (NSPrivacyAccessedAPICategoryDiskSpace) is common with download, caching, or device-info plugins. E174.1 fits checking free space before writing or cleaning up when space is short, but the API requires your app to behave differently in a way users can see, not just log a number quietly. 85F4.1 fits only if you actually display free space to the user.

The pattern across all of these: don't pick the code that sounds closest, pick the one that matches what your code observably does, because Apple can review manifest entries against actual behavior. If you're not sure which plugin is responsible for a category with no clear file hint, that's worth checking in your plugin list before guessing a reason code.

A minimal example

Here's a minimal manifest for an app that only stores its own settings locally and doesn't track:

<?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>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
</dict>
</plist>
Enter fullscreen mode Exit fullscreen mode

Set NSPrivacyTracking to true and list your tracking domains only if the app actually tracks users across other companies' apps or sites. This is a separate question from the required-reason APIs above, and it's worth double-checking your App Store Connect App Privacy answers match whatever you put here.

Where the file goes

  • Capacitor: ios/App/App/PrivacyInfo.xcprivacy, added to the App target in Xcode (File > New File > App Privacy, or drag in an existing one and check "Add to target: App" in the file inspector). It has to actually be a target member, not just sit in the folder, or Xcode won't bundle it.
  • Flutter: ios/Runner/PrivacyInfo.xcprivacy, added to the Runner target the same way.

After adding it, do a clean build and re-upload. If the earlier ITMS-91053 only listed your app file's categories and you addressed all of them, the email won't return for those categories. If it still lists a framework path, that one wasn't yours to fix with this file; go back to updating that plugin.

It's a warning until it isn't

As of when this was written, ITMS-91053 by itself is usually informational: the build still uploads. But Apple has been tightening this, and a missing or wrong manifest can turn into a Guideline 5.1.2 rejection at review, particularly when it's paired with App Privacy answers that don't match what the app does, or an SDK doing something the manifest doesn't disclose. Don't defer it because the email didn't block the upload; treat the categories it lists as a fix, not a note to file away.

If you want to skip the copy-paste plist writing: we built a free generator that takes the ITMS-91053 email text, shows you which categories came from your app versus a framework, and walks you through reason codes with plain-English explanations for each. No signup, and nothing is sent off your device: https://neumax-labs.github.io/privacy-manifest.html

Related, if you build with Claude Code: the ebook Shipping with Claude Code and the Claude Code harness pack.

Top comments (0)