<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ship Desk</title>
    <description>The latest articles on DEV Community by Ship Desk (@shipdesk).</description>
    <link>https://dev.to/shipdesk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4144615%2F3a0c10f2-0fb2-4a96-b85b-8cbbb24ac7c2.png</url>
      <title>DEV Community: Ship Desk</title>
      <link>https://dev.to/shipdesk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shipdesk"/>
    <language>en</language>
    <item>
      <title>Fixing ITMS-91053 "Missing API declaration" in a Capacitor/Flutter app: a plain guide to PrivacyInfo.xcprivacy</title>
      <dc:creator>Ship Desk</dc:creator>
      <pubDate>Tue, 13 Oct 2026 12:25:12 +0000</pubDate>
      <link>https://dev.to/shipdesk/fixing-itms-91053-missing-api-declaration-in-a-capacitorflutter-app-a-plain-guide-to-540n</link>
      <guid>https://dev.to/shipdesk/fixing-itms-91053-missing-api-declaration-in-a-capacitorflutter-app-a-plain-guide-to-540n</guid>
      <description>&lt;p&gt;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:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the email is actually telling you
&lt;/h2&gt;

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

&lt;p&gt;That file name matters more than people expect. It's usually one of two things:&lt;/p&gt;

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

&lt;p&gt;The distinction decides who has to fix it. If Apple points at your app file, the fix belongs in your app's own &lt;code&gt;PrivacyInfo.xcprivacy&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;npx cap sync ios&lt;/code&gt; after updating pulls that in. If the flagged framework name matches a Capacitor plugin package rather than the core, update that specific &lt;code&gt;@capacitor/*&lt;/code&gt; package.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's your own app's job
&lt;/h2&gt;

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

&lt;p&gt;The most common category in wrapped apps is user defaults (&lt;code&gt;NSPrivacyAccessedAPICategoryUserDefaults&lt;/code&gt;), usually triggered by &lt;code&gt;@capacitor/preferences&lt;/code&gt;, Flutter's &lt;code&gt;shared_preferences&lt;/code&gt;, or similar storage plugins. If your app only reads and writes its own settings, the fitting reason is &lt;code&gt;CA92.1&lt;/code&gt;, "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 &lt;code&gt;1C8F.1&lt;/code&gt; 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.&lt;/p&gt;

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

&lt;p&gt;System boot time (&lt;code&gt;NSPrivacyAccessedAPICategorySystemBootTime&lt;/code&gt;) usually comes from timers, animations, or analytics SDKs measuring elapsed durations. &lt;code&gt;35F9.1&lt;/code&gt; covers measuring time between in-app events; &lt;code&gt;8FFB.1&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Disk space (&lt;code&gt;NSPrivacyAccessedAPICategoryDiskSpace&lt;/code&gt;) is common with download, caching, or device-info plugins. &lt;code&gt;E174.1&lt;/code&gt; 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. &lt;code&gt;85F4.1&lt;/code&gt; fits only if you actually display free space to the user.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal example
&lt;/h2&gt;

&lt;p&gt;Here's a minimal manifest for an app that only stores its own settings locally and doesn't track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyTracking&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyTrackingDomains&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyCollectedDataTypes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyAccessedAPITypes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyAccessedAPIType&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;NSPrivacyAccessedAPICategoryUserDefaults&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;NSPrivacyAccessedAPITypeReasons&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;CA92.1&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;NSPrivacyTracking&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the file goes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capacitor&lt;/strong&gt;: &lt;code&gt;ios/App/App/PrivacyInfo.xcprivacy&lt;/code&gt;, added to the &lt;code&gt;App&lt;/code&gt; target in Xcode (File &amp;gt; New File &amp;gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flutter&lt;/strong&gt;: &lt;code&gt;ios/Runner/PrivacyInfo.xcprivacy&lt;/code&gt;, added to the &lt;code&gt;Runner&lt;/code&gt; target the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's a warning until it isn't
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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: &lt;a href="https://neumax-labs.github.io/privacy-manifest.html" rel="noopener noreferrer"&gt;https://neumax-labs.github.io/privacy-manifest.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Related, if you build with Claude Code: the ebook &lt;a href="https://rowanhalebooks.gumroad.com/l/ekria" rel="noopener noreferrer"&gt;Shipping with Claude Code&lt;/a&gt; and the &lt;a href="https://rowanhalebooks.gumroad.com/l/claude-code-harness-pack" rel="noopener noreferrer"&gt;Claude Code harness pack&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>flutter</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How to write App Review notes that stop the "Information Needed" rejection (Guideline 2.1)</title>
      <dc:creator>Ship Desk</dc:creator>
      <pubDate>Tue, 29 Sep 2026 13:01:06 +0000</pubDate>
      <link>https://dev.to/shipdesk/how-to-write-app-review-notes-that-stop-the-information-needed-rejection-guideline-21-2e39</link>
      <guid>https://dev.to/shipdesk/how-to-write-app-review-notes-that-stop-the-information-needed-rejection-guideline-21-2e39</guid>
      <description>&lt;p&gt;If you shipped an app from Lovable, Bolt, v0, or Replit and got a Guideline 2.1 rejection that just says "Information Needed," the good news is that it's usually not a bug. It's a question. The reviewer could not get far enough into your app to judge it, and in most cases that means they hit a login screen with nothing to sign in with.&lt;/p&gt;

&lt;p&gt;This one is worth fixing carefully, because it's avoidable in a way crashes aren't. A crash needs debugging. A missing demo account just needs you to fill out a form correctly before you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens with AI-built apps specifically
&lt;/h2&gt;

&lt;p&gt;Builder-tool apps tend to get tested by exactly one person: the person who built it, already signed in, on their own phone, with their own data already in the app. The reviewer is the opposite of that. They are a stranger, on a device you probably didn't test on (iPad shows up a lot), with a brand-new account and zero saved state.&lt;/p&gt;

&lt;p&gt;That gap between "works for me" and "works for a stranger with nothing" produces most of the Information Needed findings we've seen. The App Review Information section in App Store Connect is supposed to close that gap, but it often gets left half-filled when you're rushing a submission out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reviewer actually needs from you
&lt;/h2&gt;

&lt;p&gt;Five things, roughly in the order they'll hit them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A demo account that works on a fresh device, right now.&lt;/strong&gt; Not the account you used six weeks ago. Not an account behind email verification or an SMS code. A reviewer cannot check their own phone or a shared inbox for a code, so any account that requires one is functionally the same as no account at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Where the paid features are.&lt;/strong&gt; If your app has a subscription or in-app purchase, tell the reviewer how to reach it. Don't make them guess through five screens to find the paywall, and don't assume they'll stumble into it the way an ordinary user might.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. How to reach account deletion.&lt;/strong&gt; This is a separate guideline (5.1.1(v)) but reviewers often check it in the same pass, and if your demo account doesn't have visible access to whatever menu holds it, that's a second rejection stacked on the first one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. What any hardware feature does, and how to test it without the hardware.&lt;/strong&gt; If your app uses a barcode scanner, Bluetooth device, or camera-based feature tied to physical equipment the reviewer doesn't have, explain in the notes what the feature does and, if possible, provide a way to see the result without the hardware (a demo mode, sample data, a video isn't a replacement but a clear text description is better than silence).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A sandbox purchase path that actually completes.&lt;/strong&gt; If you have in-app purchases, the reviewer may try to buy one. Make sure the sandbox product IDs match what's in App Store Connect and that a purchase completes end to end, since a paywall that errors out or spins forever produces a related but separate rejection (2.1(b)), and it's worth ruling out before you submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  A copy-paste notes template
&lt;/h2&gt;

&lt;p&gt;Fill in the brackets and delete anything that doesn't apply to your app. Keep it to three to five lines if you can; a wall of text gets skimmed, not read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Demo account:
Username: [demo email or username]
Password: [current password, tested today]

After signing in, tap [screen/tab name] to see [the main feature].

Paid features are under [path, e.g. "Settings &amp;gt; Upgrade"]. The demo
account is on the [free / trial / active subscription] plan.

Account deletion is under [path, e.g. "Settings &amp;gt; Account &amp;gt; Delete
Account"].

[If applicable] This app uses [hardware, e.g. a Bluetooth scale /
barcode scanner]. Without the hardware connected, [what the reviewer
will see instead, or how to see sample results].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before you submit, log in with that exact account on a device you haven't touched in a while, in a fresh browser session or after signing out completely. If anything in the notes is stale, the reviewer will find it before you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four mistakes that cause this rejection even when the notes exist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The demo password expired.&lt;/strong&gt; A lot of these accounts get created once, at launch, and never touched again. If your backend expires passwords or sessions after 90 days, or if the account got swept up in a "clean up test users" script, the reviewer hits a login error and reports it as Information Needed even though notes were technically provided.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two-factor authentication is on for the demo account.&lt;/strong&gt; If your auth provider defaults to requiring 2FA for new accounts, that setting can quietly apply to your reviewer account too. The reviewer has no way to receive that code, and there's no field in App Store Connect for you to hand it to them. Turn 2FA off for this one account specifically, or build a separate reviewer role that bypasses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The notes describe a screen that moved.&lt;/strong&gt; You wrote the notes for version 1.0, and by the time review actually happens you're on 1.3 with a redesigned nav bar. "Tap the third icon on the bottom" is now wrong. Notes describing paths through the UI go stale fast if you're shipping frequently, so it's worth a thirty-second check on every submission rather than assuming last time's notes still apply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marketing copy pasted into the notes field.&lt;/strong&gt; The App Review Information notes field is not the App Store description. A reviewer reading "Revolutionary AI-powered productivity for everyone!" instead of "tap Projects to see the main feature" has to guess where to click, and guessing is exactly what produces the rejection you're trying to avoid. Write the notes like you're handing a stranger a set of directions, because that's what they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  One more thing worth knowing
&lt;/h2&gt;

&lt;p&gt;Not every Information Needed rejection requires a new build. Once you fix the App Review Information (demo account, notes, whatever was missing), you can usually reply in the Resolution Center without resubmitting a build at all. Save the new-build cycle for actual bugs; a missing or broken login is a paperwork fix, not a code fix.&lt;/p&gt;

&lt;p&gt;If you want the fuller breakdown of what a 2.1 rejection can mean beyond Information Needed, including crashes, broken flows, and purchases the reviewer couldn't reach, I wrote a longer reference here: &lt;a href="https://neumax-labs.github.io/guideline/guideline-2-1-app-completeness.html" rel="noopener noreferrer"&gt;https://neumax-labs.github.io/guideline/guideline-2-1-app-completeness.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>nocode</category>
      <category>mobile</category>
    </item>
    <item>
      <title>7 two-minute tests to run before App Review (for Lovable/Bolt/v0/Replit apps)</title>
      <dc:creator>Ship Desk</dc:creator>
      <pubDate>Sun, 27 Sep 2026 13:03:44 +0000</pubDate>
      <link>https://dev.to/shipdesk/7-two-minute-tests-to-run-before-app-review-for-lovableboltv0replit-apps-1ikb</link>
      <guid>https://dev.to/shipdesk/7-two-minute-tests-to-run-before-app-review-for-lovableboltv0replit-apps-1ikb</guid>
      <description>&lt;p&gt;If you shipped your app with an AI builder, there's a good chance you've only ever opened it as yourself: same phone, same account, already signed in, always online. That's exactly the situation App Review never tests. A reviewer is a stranger with a fresh device, no history, and a habit of doing the one thing your happy path never expects.&lt;/p&gt;

&lt;p&gt;Most rejections we've seen for AI-built apps aren't about missing features. They're about a two-minute scenario nobody ran before hitting submit. Here are seven of them, each cheap enough to run tonight.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. First launch in airplane mode
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Delete the app, reinstall it, turn on airplane mode, then open it for the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; A blank white screen, an infinite spinner, or a crash. Nothing tells the person what happened or what to do next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; A reviewer sometimes tests on a poor or absent connection, and a failed first fetch that leaves a blank screen reads as a crash, not a network hiccup. This is a fast, common failure in apps built by wrapping a web app in a native shell, because the shell's first load usually depends on a network call that has no fallback. The fix is small: catch the failed request and show a message with a retry button. A reader pointed out this exact test after reading our full checklist, and it's now one of the fastest ways to fail an app that otherwise works fine.&lt;/p&gt;

&lt;p&gt;This falls under Guideline 4.2, minimum functionality: whether the app is "sufficiently different from a mobile browsing experience" and behaves like a native app rather than a website with no error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Fresh install on an iPad
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; If you built for iPhone, install the app on an iPad (a simulator is fine) and check both orientations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; Layouts that overlap, buttons cut off at the edge, text that doesn't reflow, or a screen that only works in portrait when the reviewer tries landscape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Review often runs on an iPad even for apps that were designed and tested only on iPhone. Many builders assume the reviewer uses the same device class you developed on. They frequently don't, and a broken iPad layout is treated the same as a broken iPhone layout: an app that doesn't work is an app that doesn't work, regardless of which guideline number ends up on the rejection.&lt;/p&gt;

&lt;p&gt;This can land under Guideline 2.1, app completeness, the umbrella category for "the app doesn't function as submitted."&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Sign up as a stranger on the free plan
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Create a brand-new account using an identity you've never used with the app before, ideally a fresh Apple ID with "Hide My Email" turned on, and go through the free tier, not your own admin or test account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; The sign-in call succeeds, but the screen doesn't change until you force-quit and relaunch. Or the account lands in a state your team never designed for, like a plan with no default data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; We've seen a real case where sign-in technically worked but the UI stayed frozen on the login screen until the app was restarted, and Apple reported it simply as "not responsive." Your own account has weeks of history and cached state that hides this. A stranger's account has none of that, so it exposes the empty states you never built a screen for.&lt;/p&gt;

&lt;p&gt;If the app offers Google or another third-party login, this test also covers Guideline 4.8: Sign in with Apple needs to be offered as an equal option, using Apple's button style, and it needs to actually respond when tapped.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Delete the account from inside the app
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Find the account deletion option and use it, end to end, on a real device, then confirm the account is actually gone rather than just logged out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; There's no deletion option at all, or it only signs the user out, or it's buried three menus deep in a place nobody would look, or it just deactivates a flag in the database without deleting anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; Guideline 5.1.1(v) requires that any app supporting account creation must let users initiate deletion from inside the app, and Apple sometimes asks for a screen recording of the flow on a physical device. AI builders generate sign-up and log-out by default because those are the common tutorial flows; deletion is rarely part of the template, and when someone adds it later, it tends to end up in the wrong place or short of "actually delete the data."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Restore purchases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; If you sell anything through In-App Purchase, buy it on a sandbox account, delete and reinstall the app, then tap "Restore Purchases" instead of buying again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; Nothing happens, the button doesn't exist, or the restored state doesn't match what the person actually paid for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This sits close to Guideline 3.1.1, in-app purchase. Product IDs need to match exactly between App Store Connect and your billing code, and a mismatch is what makes a paywall come up empty or a restore silently fail for the reviewer. It's also good practice: a real user who reinstalls and can't get back what they paid for is a fast route to a one-star review and a refund request.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Deny every permission prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Go through the whole app and tap "Don't Allow" on every permission request: camera, photos, location, microphone, contacts, notifications, whatever the app asks for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; A feature that just silently does nothing, a crash, or a dead-end screen with no way to grant the permission later without deleting the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This connects to Guideline 5.1.2 privacy questions and general completeness. Every prompt is supposed to explain, in a full sentence, why it's asking, something like "Used to attach photos you choose to your notes," not just the OS default text, and only when the user reaches that feature, not all at once at launch. Beyond the wording, the app still needs to work, or at least fail gracefully, when someone says no. Reviewers deny prompts more often than typical users do, since it's a standard part of how they test.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Demo account from the review notes, on a fresh device
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt; Take the demo credentials exactly as you wrote them in App Review Information, and log in with them on a device or simulator you haven't touched before, following only what the notes say.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What failure looks like:&lt;/strong&gt; The password has expired or was tied to a test environment that got reset, the account triggers an SMS or email verification step nobody mentioned, or the notes describe a screen that no longer exists because the app changed since you wrote them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; A missing or broken demo login is one of the more common causes of an "Information Needed" hold under Guideline 2.1, and it's entirely self-inflicted. The credentials were fine when written, but nobody rechecked them against the build actually being submitted. If the account needs email or SMS verification to get past login, it isn't a working demo account, and reviewers won't chase down a second factor to get in.&lt;/p&gt;




&lt;p&gt;None of these take longer than a coffee break, and together they catch the failures that show up disguised as random-looking guideline numbers on a rejection. The full pre-submission list these came from, with plain-language explanations of each guideline, is free here: &lt;a href="https://neumax-labs.github.io/checklist.html" rel="noopener noreferrer"&gt;https://neumax-labs.github.io/checklist.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>nocode</category>
      <category>testing</category>
    </item>
    <item>
      <title>What each App Store rejection guideline actually means for AI-built apps</title>
      <dc:creator>Ship Desk</dc:creator>
      <pubDate>Sat, 26 Sep 2026 17:12:13 +0000</pubDate>
      <link>https://dev.to/shipdesk/what-each-app-store-rejection-guideline-actually-means-for-ai-built-apps-3j6k</link>
      <guid>https://dev.to/shipdesk/what-each-app-store-rejection-guideline-actually-means-for-ai-built-apps-3j6k</guid>
      <description>&lt;p&gt;If you shipped your app with Lovable, Base44, Bolt, v0, Replit, or another AI app builder — or you're driving Claude Code / another coding agent directly — you'll eventually hit one of a small set of App Store rejections that show up over and over for this category of app. The guideline number Apple prints is a category, not a diagnosis, and treating it as a diagnosis is the single most expensive mistake you can make: it costs a full review cycle (typically 2-4 days) per wrong guess.&lt;/p&gt;

&lt;p&gt;This is a plain-language breakdown of the five guidelines that account for most rejections in AI-built apps: 2.1 (App Completeness), 4.2 (Minimum Functionality), 4.8 (Login Services), 5.1.1(v) (Account Deletion), and 3.1.1 (In-App Purchase). For each one: what Apple actually means, why it shows up disproportionately in AI-built apps, and what fixing it actually looks like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI-built apps hit these specific five
&lt;/h2&gt;

&lt;p&gt;There's a pattern behind why these guidelines cluster together for this category of app, and it's worth naming before going through them individually.&lt;/p&gt;

&lt;p&gt;Apps built quickly with an AI builder or coding agent tend to pass their &lt;em&gt;first&lt;/em&gt; review and fail later ones. The first version usually has a small "review surface" — maybe just email sign-in, no payments, no social login. Then over a few weeks the surface grows: Google sign-in gets added, then Apple sign-in, then in-app purchases, then an invite-code system. Each addition doubles what a reviewer can touch. And because testing tends to mean "click through it myself, logged in as me," nobody walks the &lt;em&gt;new&lt;/em&gt; areas the way a stranger with a brand-new account would.&lt;/p&gt;

&lt;p&gt;None of the five guidelines below are really about code quality. They're about what a reviewer, acting as a first-time user with no history in the app, can and cannot do.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 — App Completeness
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it actually means:&lt;/strong&gt; the reviewer hit something that didn't work — a crash, a button that does nothing, an endless spinner, a blank screen, a link to nowhere. Apple reviews on real, current devices, often an iPad even if you only designed for iPhone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI-built apps hit this:&lt;/strong&gt; the most common cause isn't a bug in the traditional sense — it's a login or sign-up path that works fine for the account you've been testing with (which has months of history and every permission already granted) but fails for an account created fresh, thirty seconds before the reviewer clicks the next button. A second common cause: the layout was designed and tested on a phone simulator and never opened on an iPad, so a control is off-screen or covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "fixed" actually looks like:&lt;/strong&gt; get the exact device and OS from the rejection notice (open the submission in App Store Connect and read the full message — the summary email and the version-page banner are not the full text). Walk the exact path the reviewer describes with a brand-new account, not your logged-in one. If you genuinely cannot reproduce it, don't guess and resubmit — reply and ask for the exact reproduction steps. Apple's follow-up often names the real cause in one sentence, and that sentence is worth more than three days of guessing.&lt;/p&gt;

&lt;p&gt;One thing worth knowing before you guess: a wrong hypothesis chased for days is a common trap here. If the rejection says "not responsive" and you can't reproduce it, the honest reply — "we could not reproduce this on [devices tested]; could you share the exact steps?" — is a better use of a review cycle than shipping five plausible-looking fixes and hoping one of them was right.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.2 — Minimum Functionality
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it actually means:&lt;/strong&gt; Apple thinks the app is a website in a wrapper, offering nothing a browser tab doesn't already give you. This is the single most common rejection specifically for apps made with web-based AI builders and then wrapped for iOS (Capacitor, WebView-based shells, etc.) — it exists almost entirely for this category of app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI-built apps hit this:&lt;/strong&gt; the builder generates a responsive web app, and the path to "iOS app" is often just wrapping that same web app in a native shell with no additional native behavior. Apple can tell. Browser-style navigation (back arrows, links opening inside the wrapper), a blank white screen with no connection instead of an offline state, and zero use of anything the phone actually offers (notifications, camera, share sheet, haptics) are the tells.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "fixed" actually looks like:&lt;/strong&gt; this isn't a bug fix, it's an addition. You need at least one or two things that are specific to being on a phone: push notifications when something relevant happens, camera or photo input, a proper offline screen with cached content instead of blank white, a native tab bar instead of the website's header/footer. When you reply to Apple, name the specific native features you added — "we improved the app" gets you the same rejection again. "Build 1.2 adds push notifications when a booking is confirmed, offline access to saved items, and share-sheet export of reports" gets read.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.8 — Login Services
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it actually means:&lt;/strong&gt; you offer a third-party login (Google, Facebook, etc.) but not one that meets Apple's privacy conditions — in practice, Sign in with Apple. Or Sign in with Apple exists but is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI-built apps hit this:&lt;/strong&gt; most auth templates generated by builders default to "Continue with Google" because it's the most common OAuth example in training data and documentation, and nobody adds the Apple equivalent until Apple asks for it. It's genuinely one of the cheaper fixes on this list — most auth providers (Supabase, Firebase, and others) support Sign in with Apple as a checkbox-level feature — but it has to be wired into your entitlements file and your backend's auth settings, not just added as a button.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A specific failure mode worth knowing in advance:&lt;/strong&gt; Sign in with Apple can be technically working — the user genuinely signs in — while the app never tells the screen. This happens when the app decides "is this user signed in?" by checking a profile record that loads a moment &lt;em&gt;after&lt;/em&gt; the sign-in session is created, rather than checking the session itself. Email/password sign-in often accidentally avoids this because of how the code happens to sequence, while Apple sign-in exposes the race. From the reviewer's side, this looks exactly like "not responsive" — tap the button, sign in successfully, nothing happens until you force-relaunch the app. If Apple reports Sign in with Apple as unresponsive, check the step &lt;em&gt;after&lt;/em&gt; sign-in completes, not just the sign-in call itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "fixed" actually looks like:&lt;/strong&gt; add Sign in with Apple at the same size and visual weight as your other sign-in options, using Apple's actual button asset (not a generic apple icon from an icon library — reviewers, and contrast checkers, both notice). Test with an Apple ID that has genuinely never touched your app, including the "Hide My Email" relay option, and confirm the very next screen appears without a restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.1.1(v) — Account Deletion
&lt;/h2&gt;

&lt;p&gt;**What it actually means: **users can create an account inside the app but can't delete it from inside the app. Apple has required in-app account deletion since 2022 — not a support email, not a web form, an actual in-app path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI-built apps hit this:&lt;/strong&gt; the feature is very often &lt;em&gt;actually present&lt;/em&gt; and still gets rejected, because of where it's placed. A common pattern: account deletion lives on a settings screen that's only visible to users with an admin or elevated role, because "that's where settings live" in the generated UI. If the demo account provided to Apple isn't an admin, the reviewer genuinely cannot find the feature — even though it exists and works. A correct feature can fail review purely because of who's allowed to see it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "fixed" actually looks like:&lt;/strong&gt; "Delete account" needs to be reachable from every account role, not just admins — it only ever deletes the current user's own account, so there's usually no security reason to gate it. Confirm once, then actually delete (or clearly start deletion of) the account and its data, and say in the UI how long that takes. If you use Sign in with Apple, revoke the Apple token as part of deletion. In your review notes, spell out the exact path — "Settings → Account → Delete Account" — rather than assuming the reviewer will find it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.1.1 — In-App Purchase Required for Digital Content
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it actually means:&lt;/strong&gt; the app sells or unlocks digital features or content — premium features, credits, subscriptions to the app itself — using a payment method other than Apple's In-App Purchase, or links out to one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why AI-built apps hit this:&lt;/strong&gt; Stripe checkout is the default payment integration in most AI builder templates, because it's the fastest thing to wire up and it's genuinely the right call for a web product. It is very often the wrong call for the iOS build of the same product. A "Subscribe" button in the iOS app that opens a web checkout for something used inside the app is close to a guaranteed rejection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What actually needs to change, and what doesn't:&lt;/strong&gt; the rule turns on what you're selling, not how you built the app. Digital features or content consumed inside the app generally need In-App Purchase. Physical goods and real-world services — deliveries, bookings, in-person coaching — can keep using your own payment processor, because they're consumed outside the app. If you have an "invite code" or similar unlock mechanism, document exactly what it does and doesn't unlock in your App Review notes; reviewers assume the worst about anything that looks like a payment bypass unless told otherwise, explicitly.&lt;/p&gt;

&lt;p&gt;One detail that costs people a review cycle for reasons that have nothing to do with code: In-App Purchase product identifiers have to match, exactly, between what your app requests and what's registered in App Store Connect. If a note in your own project log says this was fixed, treat that as a claim about the past, not a fact about today — re-check the live dashboard, with today's date, before relying on it. And a release build that's missing the purchase configuration will show the reviewer a bare paywall error, which reads as a completeness bug even though the cause was a build step, not a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one habit that generalizes across all five
&lt;/h2&gt;

&lt;p&gt;Every one of these guidelines is really asking the same underlying question from five different angles: what does this app look like to someone with no history in it, on a device you didn't personally test on, following exactly the path described? Read the full rejection text before writing a single line of code in response — the guideline number is a category, the message underneath it is the actual finding, and conflating the two is what turns a two-day fix into a two-week one.&lt;/p&gt;

&lt;p&gt;If you want the full decision tree for a specific rejection message — which guideline it maps to, the typical causes for AI-built apps specifically, and a reply template — there's a free rejection decoder here: &lt;a href="https://neumax-labs.github.io/decoder.html" rel="noopener noreferrer"&gt;https://neumax-labs.github.io/decoder.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>nocode</category>
      <category>mobile</category>
    </item>
    <item>
      <title>The App Store checklist for apps built with Lovable/Bolt/v0</title>
      <dc:creator>Ship Desk</dc:creator>
      <pubDate>Sat, 26 Sep 2026 16:41:22 +0000</pubDate>
      <link>https://dev.to/shipdesk/the-app-store-checklist-for-apps-built-with-lovableboltv0-93</link>
      <guid>https://dev.to/shipdesk/the-app-store-checklist-for-apps-built-with-lovableboltv0-93</guid>
      <description>&lt;p&gt;If you built your app with an AI app builder (Lovable, Bolt, v0, Replit, or similar) and wrapped it for iOS with something like Capacitor, you're going to hit App Review with a specific set of blind spots. Not because the app is bad — because the tools that generated it optimize for "it works when I click through it," not "it works from a stranger's brand-new account, on the free plan, on an iPad."&lt;/p&gt;

&lt;p&gt;This is a stage-by-stage checklist built from actual rejection cycles: real guideline numbers, real causes. It won't guarantee approval — nothing does, Apple reviews every submission and makes the call — but it closes off the failure modes that keep coming back for apps built this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these apps get rejected in predictable ways
&lt;/h2&gt;

&lt;p&gt;AI-built apps tend to pass their &lt;em&gt;first&lt;/em&gt; review and fail later ones. The reason is almost always the same: the first version had a small "review surface" — maybe just email sign-in, no payments, no social login. Then over a few weeks you add Google sign-in, then Apple sign-in, then in-app purchases, then an invite-code system. Each addition doubles what a reviewer can touch. And because the builder tool (and the humans using it) tested by walking the same happy path over and over, nobody ever walked through the &lt;em&gt;new&lt;/em&gt; areas as a first-time stranger would.&lt;/p&gt;

&lt;p&gt;Keep that framing in mind as you go through this list: every stage below exists because someone, somewhere, shipped a feature that worked for them and failed for a reviewer with no history in the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1 — Before you touch sign-in
&lt;/h2&gt;

&lt;p&gt;If your app offers any third-party login (Google, Facebook, etc.), Apple's Guideline 4.8 requires you to also offer an equivalent privacy-respecting option — in practice, Sign in with Apple. This is not optional and not negotiable, and it's cheap to add early and expensive to retrofit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] If you offer any social/OAuth login, Sign in with Apple is also implemented natively (not just as a web view).&lt;/li&gt;
&lt;li&gt;[ ] Sign in with Apple is wired into your entitlements file and your backend's auth settings — not just the button on the screen.&lt;/li&gt;
&lt;li&gt;[ ] You've tested first-time sign-in with &lt;em&gt;every&lt;/em&gt; login method on a device that has never used the app before.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last item matters more than it sounds. A very common bug: the app's "is this a new user, send them to onboarding" logic was written when only one login provider existed, and never updated when a second one was added. Result: a new user via the second provider skips onboarding, has no account state set up, and every subsequent request silently fails. Nothing crashes, nothing shows an error — the screen just sits there, which to a reviewer looks exactly like "this button does nothing."&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2 — Account deletion, and who can see it
&lt;/h2&gt;

&lt;p&gt;Since 2022, Guideline 5.1.1(v) requires that any app letting users create an account must let them delete it from inside the app — not just via a support email, not just via a web page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] "Delete account" exists inside the app, not only on a website.&lt;/li&gt;
&lt;li&gt;[ ] It's visible to &lt;em&gt;every&lt;/em&gt; account role, not just admins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second point trips up more apps than you'd expect. It's common to build a settings screen that's admin-only, and then bury account deletion on it because "that's where settings live." If your test/demo account for reviewers isn't an admin, the reviewer literally cannot find the feature — even though it exists. A correct feature can still fail review because of where it's placed and who's allowed to see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3 — Payments, the part everyone gets wrong once
&lt;/h2&gt;

&lt;p&gt;Guideline 3.1.1 requires that digital features consumed inside the app be sold through Apple's in-app purchase system, with narrow exceptions (mainly for goods/services consumed outside the app). If your builder-generated app routes users to a web checkout for anything used inside the app, expect a rejection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Any paid feature usable inside the iOS app is sold via StoreKit / in-app purchase, not a web redirect.&lt;/li&gt;
&lt;li&gt;[ ] Web checkout, if you have one, is reserved for your website only — not linked or referenced from the iOS app's paywall.&lt;/li&gt;
&lt;li&gt;[ ] Any "invite code" or similar unlock mechanism is documented in your App Review notes explaining exactly what it does and does not unlock. If it doesn't bypass payment, say so explicitly — reviewers will otherwise assume the worst.&lt;/li&gt;
&lt;li&gt;[ ] Your in-app purchase products are registered in App Store Connect and the identifiers match exactly what your app requests. (Mismatches here are a classic case of a note in a project log going stale — re-verify the actual dashboard state with a date, don't trust an old comment.)&lt;/li&gt;
&lt;li&gt;[ ] You can produce a release build that includes the correct purchase configuration — a build without it will just show a paywall error to the reviewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stage 4 — Your demo account, treated as a real submission artifact
&lt;/h2&gt;

&lt;p&gt;A huge fraction of "reviewer couldn't do X" rejections trace back to the demo account, not the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The demo account in App Store Connect actually logs in, right now, today (test it fresh before every submission).&lt;/li&gt;
&lt;li&gt;[ ] "Sign-in required" is checked correctly in the App Review information if your app needs an account to do anything meaningful.&lt;/li&gt;
&lt;li&gt;[ ] Your review notes describe the &lt;em&gt;current&lt;/em&gt; demo account — not a stale one from a previous submission.&lt;/li&gt;
&lt;li&gt;[ ] If your app has subscription tiers, provide a demo account whose subscription state matches what the reviewer needs to test (e.g., an account with an expired subscription if they need to test the renewal/paywall flow).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stage 5 — Placeholder and unfinished content
&lt;/h2&gt;

&lt;p&gt;Guideline 2.1 (App Completeness) covers a wide net: placeholder content, coming-soon labels, crashes, and unfinished features. AI-generated UIs are prone to shipping placeholder text that never got swapped out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No "your logo here," "lorem ipsum," or similar placeholder strings anywhere in the build a reviewer can reach.&lt;/li&gt;
&lt;li&gt;[ ] No buttons or nav items labeled "coming soon" that lead nowhere.&lt;/li&gt;
&lt;li&gt;[ ] No "beta" language describing the app itself, unless you're actually distributing via TestFlight beta review.&lt;/li&gt;
&lt;li&gt;[ ] Every plan/tier, including the free one, has been clicked through end to end, on the cheapest plan, without crashing.&lt;/li&gt;
&lt;li&gt;[ ] Testimonials or social proof, if present, are attributed to a real, checkable source.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stage 6 — Test it as a stranger, on an iPad
&lt;/h2&gt;

&lt;p&gt;This is the stage most builder-tool workflows skip entirely, because the builder's preview is usually a phone-sized web view.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] You've done a full run-through on an actual iPad (not just resized phone simulator), on the free plan, with a brand-new account.&lt;/li&gt;
&lt;li&gt;[ ] You've done the same run-through on a physical iPhone, not just a simulator — some things (Sign in with Apple in particular) behave differently on-device.&lt;/li&gt;
&lt;li&gt;[ ] If Apple's rejection asks for a screen recording, you're prepared to record it on a real device, not a simulator driven by automation. Simulator recordings may be accepted, but a reviewer who explicitly asked for a device recording is telling you they distrust simulator evidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stage 7 — When you do get rejected: get the actual message
&lt;/h2&gt;

&lt;p&gt;This isn't a pre-submission item, but it belongs in the checklist because it changes how you spend the days after a rejection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Don't diagnose from the guideline number alone. "2.1" covers crashes, placeholders, missing features, and broken flows — it is a category, not a specific cause.&lt;/li&gt;
&lt;li&gt;[ ] Open the actual rejected submission in App Store Connect and read the full message text, including any attached screenshots and the device/OS the reviewer used. The summary email and the version page are not the full message.&lt;/li&gt;
&lt;li&gt;[ ] Note the specific device mentioned (e.g., "iPad Air, current iPadOS") — if none of your own testing covered that device, that's your first lead.&lt;/li&gt;
&lt;li&gt;[ ] Match your fix list to what was &lt;em&gt;actually said&lt;/em&gt;, not to everything you can find that might loosely relate to the guideline number. It's easy to spend days fixing seven real things when Apple only flagged three — and fixing the wrong six doesn't get you approved.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stage 8 — Before you resubmit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every item Apple's message named specifically has a corresponding fix, and you can explain the fix in one sentence in your reply.&lt;/li&gt;
&lt;li&gt;[ ] Any external dashboard state you're relying on (payment processor, backend config) has been re-checked &lt;em&gt;today&lt;/em&gt;, not assumed from an old note.&lt;/li&gt;
&lt;li&gt;[ ] Your reply to App Review states plainly what changed and, if useful, why the reviewer may have seen the described behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using this as a running list
&lt;/h2&gt;

&lt;p&gt;None of this is exotic — it's mostly things that are obvious in hindsight and invisible in the moment, because AI builder tools are very good at making the happy path work and say nothing about the paths nobody walked. Treat this less as a one-time pre-submission task and more as a standing checklist you run every time you open up a new area of the app: a new sign-in method, a new plan tier, a new role.&lt;/p&gt;

&lt;p&gt;We turned this into an interactive checklist (progress saved locally in your browser, nothing sent anywhere) if you'd rather work through it that way instead of copy-pasting a markdown list: &lt;a href="https://neumax-labs.github.io/checklist.html" rel="noopener noreferrer"&gt;https://neumax-labs.github.io/checklist.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>nocode</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
