iOS App Submission Checklist: What to Check Before You Submit
3 Apr 2026 · 14 min read · Erik van Kammen
You've been building for weeks. The feature set is solid, QA signed off, and you're staring at that blue "Submit for Review" button in App Store Connect. This is the moment where small oversights turn into multi-day delays. A missing privacy manifest entry. A permission string that says "for app functionality" instead of explaining why. An ATS exception you forgot to scope down from development.
Apple's review process combines automated checks with human review across design, content, legal compliance, and technical correctness. No single checklist covers all of it, because parts of the review are subjective. But the technical layer is predictable. These are the items that cause rejections you could have caught before submitting.
This checklist is split into two layers. The first covers what every iOS developer needs to verify, whether you're shipping your first app or your fiftieth. The second layer is for teams that want to automate and scale the process.
These items apply regardless of your framework, team size, or experience level. Miss any of them and you're looking at a rejection that takes days to resolve.
Apple requires apps to be built with a specific Xcode and SDK version. Since April 28, 2026, App Store Connect requires builds made with Xcode 26 and the iOS 26 SDK; the previous requirement was Xcode 16 with the iOS 18 SDK. Apps built with older toolchains are rejected.
Check your Xcode version before you archive. If you're using a CI service like GitHub Actions or Bitrise, verify that your build image includes the correct Xcode version. This sounds obvious, but pinned CI configurations are one of the most common causes of SDK version rejections.
Since May 2024, Apple requires a PrivacyInfo.xcprivacy file that declares which required reason APIs your app uses and why. For apps with multiple frameworks or SDK targets, see the guide on privacy manifests in multi-module iOS projects. Apple defines several categories of required reason APIs, such as file timestamps, system boot time, disk space, and UserDefaults access. Each usage needs a specific reason code, and the list may expand over time.
This isn't just about your own code. Every third-party SDK must be covered by a privacy manifest, either its own or your app-level manifest. Apple's review tooling checks this automatically, and submissions with missing declarations get rejected with error codes like ITMS-91053.
A minimal PrivacyInfo.xcprivacy declaration for UserDefaults access looks like this:
<!-- PrivacyInfo.xcprivacy: declare required reason API usage -->
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
If you're not sure which APIs your dependencies use, scan the compiled binary rather than guessing from source. The build process can pull in transitive dependencies that aren't visible in your Podfile or Package.swift.
Every permission your app requests needs a usage description string in your Info.plist. Camera access, location, photo library, microphone, contacts. Apple rejects vague descriptions like "needed for app functionality" or "required for this feature." The string should tell the user exactly what happens when they grant the permission and why.
Review every NS...UsageDescription key in your Info.plist. If you inherited permissions from an SDK you integrated months ago but don't actually use, remove them. Unnecessary permissions increase the risk of rejection.
ATS requires all network connections to use HTTPS with TLS 1.2 or higher. If you added NSAllowsArbitraryLoads = YES during development to hit a local server, that flag needs to go before submission. A global ATS exception is almost always rejected unless you have a very strong justification.
Per-domain exceptions using NSExceptionDomains are acceptable when you need to connect to a specific server that doesn't support modern TLS. But each exception should be scoped as narrowly as possible. One domain, one exception, with a clear reason if Apple asks.
Guideline 2.1 (App Completeness) is consistently one of the top rejection reasons. Apple's reviewers will reject apps with placeholder content, "coming soon" sections, broken links, or features that require a login but don't provide test credentials.
Before you submit: walk through every screen as if you've never seen the app. Check that demo accounts work in the review environment. Make sure deep links resolve. Verify that all features mentioned in your App Store description actually exist in the build.
Check for debug flags, test configurations, and development endpoints that shouldn't be in a release build. Common culprits: DEBUG preprocessor flags that made it into release schemes, hardcoded staging URLs, verbose logging that prints user data to the console, and test-only feature flags that expose unfinished UI.
Your metadata in App Store Connect needs to match what the app actually does.
If your app collects the IDFA (Identifier for Advertisers) or uses any data for tracking users across apps or websites owned by other companies, you need to implement the AppTrackingTransparency framework. The user must explicitly grant permission through the ATT prompt before you access tracking data.
This applies even if you're not directly collecting the IDFA yourself. If an SDK in your binary does it, you're responsible. Common culprits: analytics SDKs, ad networks, and attribution platforms. If your app doesn't track users at all, you don't need the prompt, but make sure your privacy manifest's NSPrivacyTracking is set to false.
Your distribution certificate and provisioning profile need to be valid at submission time. Expired certificates or mismatched entitlements between your profile and your app's capabilities will block the upload before it even reaches review. If you've recently renewed certificates or changed your team setup, rebuild and re-sign the archive.
For teams: make sure your CI/CD environment has the correct signing identity. Match signing happens in Xcode, but CI environments often use manual signing with exported profiles. A mismatch between the entitlements in your binary and what the profile allows causes silent failures during upload.
Simulator testing catches logic bugs but misses hardware-specific issues. Camera integrations, push notifications, background modes, NFC, Bluetooth, and ARKit features all behave differently on physical devices. Apple's reviewers test on real hardware. If your app crashes on a device you didn't test, you'll hear about it in the Resolution Center.
The framework you build with determines which compliance issues are most likely to trip you up.
| Framework | Most Common Issue | Key Check | Notes |
|---|---|---|---|
| SwiftUI / UIKit | Privacy manifest gaps | PrivacyInfo.xcprivacy in all targets |
UserDefaults is a required reason API |
| Expo | ATS exceptions from dev config | Remove localhost exceptions | expo-build-properties plugin |
| Flutter | Plugin-injected permissions | Review merged AndroidManifest.xml |
connectivity_plus adds Reachability |
| React Native (bare) | Missing privacy manifest file | Create PrivacyInfo.xcprivacy manually |
Static CocoaPods parsing issue |
| Unity | Missing manifest for UnityFramework | Update core packages (IAP, Analytics) | Check each Asset Store plugin |
Here's what to watch for in each major toolchain.
Expo SDK packages ship their own privacy manifest files, but Apple has a known parsing issue with PrivacyInfo.xcprivacy files from static CocoaPods dependencies. The workaround: duplicate required reason API declarations from your dependency manifests into your app-level privacy manifest. Expo's app.json config supports this through the expo-build-properties plugin.
Check that your Expo SDK version is current. Older Expo versions may not include the required privacy manifests for Apple's latest enforcement wave.
Flutter itself and several popular packages (like connectivity_plus, which uses Apple's Reachability framework) require privacy manifest entries. The most common rejection error is ITMS-91108 (Invalid Privacy Manifest). Every pub.dev package with native iOS code needs a privacy manifest. If the package author hasn't added one, you'll need to patch it or find an alternative.
Make sure the Flutter engine's own PrivacyInfo.xcprivacy is included in your Runner target. This is easy to miss after upgrading Flutter versions.
Without Expo's managed workflow, you're responsible for creating the PrivacyInfo.xcprivacy file manually in Xcode and adding it to your iOS target. The same static CocoaPods parsing issue applies.
Watch out for libraries like react-native-firebase and Sentry. These use required reason APIs and need corresponding manifest declarations. If you're still using CodePush (Microsoft retired App Center in March 2025), be aware that it loads code dynamically, which intersects with Apple's Guideline 2.5.2 restrictions on runtime code execution. Teams migrating off CodePush often move to EAS Update through Expo's managed workflow.
Native development has the fewest compliance surprises because you're working directly with Apple's frameworks and tooling. The main gotcha is permission description quality. Vague strings get rejected. Another common issue: using APIs that require privacy manifest declarations without realizing it. UserDefaults.standard is used everywhere, and it's one of the five required reason API categories.
If your app uses generative AI features, review Apple's Guideline 1.2 (Safety: User-Generated Content), which requires content moderation and reporting mechanisms for AI-generated content, and Guideline 5.1.2(i), which requires disclosure when a third-party AI service processes user data. If your app embeds a chatbot, mini-app, or plug-in container, Guideline 4.7 also applies. Apps that integrate external AI services should explain how the AI component works and provide appropriate age-gating.
Unity apps commonly trigger errors about missing privacy manifests for UnityFramework.framework. Recent versions of Unity's core packages (IAP, Services Core, Analytics) ship their own PrivacyInfo.xcprivacy files. If you're on older package versions, update before submitting. Check the Unity documentation on Apple privacy manifests for the minimum required versions.
Beyond Unity's own framework, every third-party Unity plugin with native iOS code needs its own privacy manifest. The Unity Asset Store doesn't enforce this, so you'll need to audit your plugins individually.
If you're shipping regularly, or managing multiple apps, manual pre-submission checks don't scale. This layer is about building verification into your process so issues get caught before someone clicks submit.
The most impactful change a team can make is scanning the compiled binary as a post-build step. Not the source code. The binary. Source code analysis misses transitive dependencies, build-time injections, and framework-level changes that only appear in the compiled artifact.
AppCompliance runs against your IPA, APK, or AAB file and checks the technically verifiable layer: privacy manifests, permissions, ATS configuration, SDK inventory, embedded fonts, target SDK version, and more. It integrates with GitHub Actions, Bitrise, CircleCI, Jenkins, and other CI platforms as a post-build gate. A scan takes minutes, and it catches the binary-level issues that cause predictable rejections.
No tool covers Apple's full review process. Design quality, content policy, and UX guidelines are evaluated by human reviewers and can't be automated. But the technical compliance layer is deterministic: either your privacy manifest is complete or it isn't. Either your ATS configuration is scoped correctly or it isn't. Automating that layer removes an entire category of rejection risk from your release process.
When you update an SDK, its behavior can change in ways your source diff won't show. A new version of an analytics library might start collecting device identifiers. A networking SDK might add a tracking domain. A payment library might introduce a new permission.
Compare your binary's SDK inventory between releases. If a new SDK appears, or an existing one changes its permission or API footprint, that's a compliance-relevant change that needs review before submission. Automating this comparison as part of your release process catches regressions that manual review consistently misses.
If your organization requires compliance documentation, export a Software Bill of Materials from each release build. SBOM formats like CycloneDX and SPDX provide a machine-readable inventory of every component in your binary, including version numbers and license information.
This isn't just for enterprise compliance teams. If Apple or a font foundry or an open-source licensor asks what's in your binary, an SBOM gives you a defensible answer that doesn't require reverse-engineering your own build.
Instead of reviewing scan results manually, set a policy: block the release if any critical or high-severity finding is present. Let medium and low findings through with a notification. This turns compliance scanning from a report you might read into a gate that stops problematic builds from reaching App Store Connect.
The threshold is a team decision. Some teams gate on critical only. Others gate on critical and high but allow exceptions through an accepted-risk workflow. The point is making the decision explicit rather than relying on someone remembering to check.
Don't wait until the release branch to discover compliance issues. If your CI pipeline builds an IPA on pull requests (even a debug build), scan it. Privacy manifest issues, permission changes, and SDK additions show up in development builds too. Catching them at the PR level means the fix lands in the same branch, not as a hotfix after a rejection.
Right before you submit, run through this compressed version. Print it out, tape it to your monitor, or add it as a PR template.
Binary level:
- [ ] Built with required Xcode / SDK version
- [ ] Privacy manifest includes all required reason API declarations
- [ ] Every third-party SDK has privacy manifest coverage
- [ ] No global ATS exceptions (NSAllowsArbitraryLoads is not YES)
- [ ] No over-declared permissions in Info.plist
- [ ] Every permission has a specific, descriptive usage string
- [ ] No debug flags, staging URLs, or test configurations in the release build
- [ ] Target deployment version is intentional (not accidentally set to latest-only)
- [ ] ATT prompt implemented if any SDK tracks users (or NSPrivacyTracking set to false)
- [ ] Distribution certificate and provisioning profile are valid and match entitlements
- [ ] Export compliance questionnaire answered correctly in App Store Connect
App Store Connect: - [ ] Screenshots match the current build on required device sizes - [ ] Privacy policy URL is live and accessible - [ ] Age rating reflects current content (updated after January 2026 changes) - [ ] Description matches actual functionality - [ ] Test account credentials provided in the review notes if login is required - [ ] Contact information is current
If using a cross-platform framework: - [ ] Framework-specific privacy manifest workarounds applied (Expo, Flutter, RN, Unity) - [ ] Native plugins audited for privacy manifest compliance - [ ] Framework version supports current App Store requirements
For teams with CI/CD: - [ ] Post-build binary scan passes with no critical findings - [ ] SDK inventory reviewed for changes since last release - [ ] Gating policy enforced (or explicitly overridden with documented reason)
Each item on this list maps to a rejection category that Apple's automated and manual review process checks. None of them require guesswork. They're all verifiable before you submit.
AppCompliance automates the binary-level section of this checklist for IPA, APK, and AAB files. It doesn't replace the human review items, the metadata checks, or the product-level evaluation that Apple's reviewers perform. But it removes the category of rejections that shouldn't happen in the first place: the ones where the answer was in the binary all along.
Store review is part automated checks, part human judgment. You can't control the human judgment part. But you can make sure the automated part never surprises you.
Everything technical in this post is checkable from the build itself, and that is exactly what AppCompliance does: upload an iOS or Android build, and minutes later you have every issue that would get it rejected, with the exact fix next to it.
One email when we open the doors. No spam, just the launch.