The Shelf / App Store / App Review Pre-Submit Checklist
App Review Pre-Submit Checklist
The last check before you upload. Every item on it is there because a real submission failed without it.
The job: find your mistakes before Apple does.
A rejection costs you a review cycle — a day or more of dead time, and sometimes it drags a second problem out with it. The ones that get people are boring. A switch on the paywall that turns the free trial on and off. A Terms link the reviewer never saw. A subscription that said "Ready to Submit" but was never attached to the version you sent. A privacy form filled in and never published. This is the last check your agent runs before you upload — five to ten minutes.
How it was built: the hard way. Every item on it was added after a real submission failed without it. The scars are still on the section headers. The icon check exists because a placeholder icon — two letters on a gradient — passed a "does the file exist" check and got rejected on sight. The store-description check exists because two separate submissions had that check written down and never actually ran it.
What's on the tag:
- The paywall rules Apple rejects on, with the exact text to search your code for — the two patterns that used to be treated as smart conversion tricks and now get apps turned away, plus how big the real price has to look next to the trial, and how big and how dark the Terms link has to be.
- The traps the simulator cannot show you: home-screen widgets whose taps are dead on a real phone, a feature whose code is done but whose Apple permission was never switched on, and web addresses that work on your Mac and fail on every device.
- The audit of your App Store Connect account: purchases attached to the version (and why a first-time purchase can only be attached on the website — every scripted shortcut that fails is written down so your agent does not lose an hour finding out), intro offers per country, price and availability, and the fields Apple's own system reports as fine when they are not.
- The extra checks a Watch app drags in, the rules for games (odds on a mystery chest shown before anyone buys, test ads left switched on, Apple's rules for asking a user to rate you), a check that every web address matches everywhere it appears, and one last run-through before you build.
- The full script kit for App Store Connect: how to make the sign-in key, the web addresses your agent calls, ready-made steps to submit or cancel, and scripts that fix what they find.
Who it's issued to: iOS developers who actually ship — especially anyone selling a subscription, which is where most of the damage lands — and anyone staring down a first submission with in-app purchases attached.
Why not a free checklist off the internet? Free ones reword Apple's rules. This one carries what the rules leave out: where Apple's own system tells you everything is fine when it is not, which gates only appear at the moment you click submit, and which fixes actually cleared real rejections. It is maintained — when Apple adds a step or moves a field, the checklist gets updated and the new version lands in your locker.
Output is plain: pass or fail on every item, the file and line it is in, the exact fix, and how bad it is. Anything marked a blocker stops the upload.
FIELD REPORT real output, not a promise
Three findings in the checklist's own output format, each drawn from a documented real rejection (or near-miss) that the corresponding check now catches. App names anonymized; guideline citations and fixes as documented.
Finding 1 — Section A0: trial toggle on the paywall
CHECK: A0 — trial toggle (3.1.2(c))
RESULT: FAIL — BLOCKER
EVIDENCE: PaywallView.swift — SwiftUI Toggle bound to $trialEnabled adds/removes
the free trial on the offer card
FIX: Remove the toggle. One clear offer; the trial rides on the StoreKit
intro offer and reads as subordinate text: "7 days free, then
$59.99/year automatically. Auto-renews until cancelled."
Origin: a quit-habit app's v1.0 was rejected with this exact pattern on the paywall — a pattern that was considered a conversion best practice until the rejection landed. The same review also flagged a post-cancel "win-back" sheet under Guideline 5.6; both are hard blockers in the checklist now.
Finding 2 — Section D.5: IAPs not attached to the version
CHECK: D.5 — IAP-to-version linkage (2.1(b))
RESULT: FAIL — BLOCKER
EVIDENCE: 3 products referenced in SubscriptionManager.swift, all "Ready to
Submit" in ASC — none listed in the version page's "In-App Purchases
and Subscriptions" table
FIX: First-time IAPs cannot be bundled via the public API (all four
submission endpoints tested: schema rejection or 409
FIRST_*_MUST_BE_SUBMITTED_ON_VERSION). Use the web UI: version page →
select all IAPs → Add for Review → Submit. Verify each product's
state flips to WAITING_FOR_REVIEW via the API afterwards.
Origin: a first-launch build submitted via the API alone was rejected under 2.1(b) roughly ten hours later — the reviewer could not locate the IAPs. The rebuilt submission used the web-UI flow and the version plus all three IAPs entered review together.
Finding 3 — Section A2: EULA link missing from the store description
CHECK: A2 — Terms of Use in the App Description (3.1.2)
RESULT: FAIL — BLOCKER
EVIDENCE: grep -c 'stdeula' <live description> → 0. The binary paywall's
Terms link is present and compliant; the description text has no
EULA link.
FIX: Add to the description:
"Terms of Use (EULA): https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
plus the standard auto-renewal disclosure paragraph.
Origin: a subscription app carried both a 2.1(b) IAP-linkage rejection AND this independent 3.1.2(c) miss in the same review. Attaching the IAPs did not clear it — the description needed its own edit, costing an extra full review cycle. The checklist now forces the description grep to actually run even when the paywall itself is compliant.
That's the shape of every run: PASS/FAIL per item, evidence at file or field level, the exact fix, and a hard stop on the archive until every blocker clears.
SERVICE RECORD living gear — updated as the factory learns
v1.1.0 — 2026-08-25
Field update from a summer of real submissions — subscription apps, a watch companion, a games title, and two rejections that taught new checks.
- CORRECTED: the web-UI flow for first-time IAP bundling. Apple removed the version page's "In-App Purchases and Subscriptions" picker; attachment is now one "Add for Review" click PER PRODUCT into a shared draft, with a greyed-submit render quirk documented. The old steps sent you hunting for a control that no longer exists.
- New rule: "first IAP must ride the version" is per product TYPE, not per app — an app with approved subscriptions still gets kicked to the web UI for its first non-renewing product; the three 409 error codes to recognize are listed.
- The silent Add-for-Review no-op: an empty Copyright field can make the button do nothing, with no message anywhere — and the general technique that names the real reason (make the same call via the API and read
meta.associatedErrors). - Sharper URL checks: a 200 that serves the homepage (byte-size tell), the Support URL as its own separately-broken field, and the full-
https://EULA grep that catches a schemeless link a bare-token grep waves through (measured on a real rejected description). - New copy-truth checks: unenforced numbers and device names in store copy, free-tier claims vs. gating code, the app's own Terms of Use as a contradiction source, blanket auto-renew copy falsified by adding a one-time product, and hardcoded-enumeration copy that expires when coverage grows.
- New screenshot checks: shots captured on a Pro build that free users see empty, and the display-change rule — if what the app shows changed, the listing is stale by construction and no repo-side gate can see it.
- New paywall checks: an undisclosed intro offer (the reverse direction of the existing sync check), and trial-eligibility flags that must fail closed.
- App Privacy: published is not true — how a "Data Not Collected" badge gets voided by a shared SDK's launch poller or your own device-id header, and why you read the server handler before deleting an identifier.
- New archive-stage gates: the archive as the first Release compile (
#if DEBUGscope trap), StoreKit purchase suites hanging simulator test runs, generated-project resource counts, reading the archive log from a file, and the iPad truncation walk (Apple reviews iPhone apps on an iPad). - Judgment rule for A0 hits on already-approved code: surface with the count of approved releases and let the owner decide — don't auto-delete a live revenue feature.
- Icon check upgraded: contact-sheet judging with the live icon as control, and a measured fill target for generated icons.
v1.0.0 — 2026-07-17
First issue. Ported from the factory's internal skill: sanitized for general use, methodology intact, field report captured from a real run.
Every update ships free to owners — your locker always serves the latest version.
QUESTIONS
Is this just Apple's review rules reworded?›
No. Apple's rules tell you what is not allowed. This tells you where apps actually trip — including App Store Connect problems the rules never mention, like purchases that were never attached to your version, offers that never reached Apple's servers, and an app cleared for sale with no countries turned on.
Do I need special access to App Store Connect?›
The checks on your code run anywhere your agent can search files. The checks on your Apple account work best with an access key — setup is included and takes minutes — and the checklist marks the handful of things only the website can confirm.
My app has no subscriptions — is it still useful?›
Yes. The subscription section skips itself. Crashes, privacy, images, review notes, version numbers and the games rules apply to every app.
Which agents does it run on?›
Written for Claude Code. It also works in Codex setups and as a reading guide in claude.ai Projects. Plain markdown, plain search commands, plain Python for the App Store Connect calls.