Pre-submission checklist for iOS App Store Connect - catches common rejection reasons
Validates App Store Connect configuration before submission to catch common rejection reasons.
/plugin marketplace add rahulkeerthi/expo-toolkit/plugin install expo-toolkit@withqwerty[app_id or app_store_connect_url]haikuValidate App Store Connect configuration before submission to catch common rejection reasons.
Before running checks, gather information about the user's setup using AskUserQuestion:
AskUserQuestion({
questions: [
{
question: "What is your app's monetization model?",
header: "Monetization",
options: [
{
label: "Freemium (IAP/Subscriptions)",
description: "Free download with in-app purchases or subscriptions",
},
{
label: "Paid Upfront",
description: "One-time purchase price to download the app",
},
{
label: "Free (No IAP)",
description: "Completely free with no monetization",
},
],
multiSelect: false,
},
{
question: "Which paywall implementation are you using?",
header: "Paywall",
options: [
{
label: "RevenueCat Paywalls",
description: "Using RC's built-in paywall UI configured in dashboard",
},
{
label: "Custom Implementation",
description: "Custom paywall UI with RevenueCat SDK for purchases",
},
{
label: "No Paywall",
description: "No subscription/purchase UI (free app or paid upfront)",
},
],
multiSelect: false,
},
{
question: "Is your app universal (supports iPad)?",
header: "iPad",
options: [
{
label: "iPhone Only",
description: "App only runs on iPhone",
},
{
label: "Universal (iPhone + iPad)",
description: "App runs on both iPhone and iPad",
},
],
multiSelect: false,
},
],
});
Based on user responses, adjust the validation:
| Monetization | Skip These Checks |
|---|---|
| Free (No IAP) | IAP validation, RevenueCat cross-check, Paywall check |
| Paid Upfront | IAP validation, RevenueCat cross-check, Paywall check |
| Freemium | Run all checks |
| Paywall Type | Check Approach |
|---|---|
| RevenueCat Paywalls | Validate RC dashboard paywall config via browser |
| Custom | Grep codebase for implementation patterns |
| No Paywall | Skip paywall checks entirely |
| iPad Support | Screenshot Check |
|---|---|
| iPhone Only | Skip iPad Pro screenshot validation |
| Universal | Require iPad Pro 12.9" screenshots |
After setup questions, automatically detect available MCPs:
// Step 1: Test Claude in Chrome MCP (REQUIRED)
const chromeResult = mcp__claude-in-chrome__tabs_context_mcp({ createIfEmpty: true });
const hasChromeInMCP = !chromeResult.error;
// Step 2: Test RevenueCat MCP (OPTIONAL - only if freemium)
// Note: The MCP tool name varies by installation. Common patterns:
// - mcp__revenuecat__mcp_RC_get_project()
// - mcp__revenuecat-{project}__mcp_RC_get_project()
let hasRevenueCatMCP = false;
if (monetization === "Freemium") {
try {
const rcResult = mcp__revenuecat__mcp_RC_get_project();
hasRevenueCatMCP = !rcResult.error;
} catch {
hasRevenueCatMCP = false;
}
}
Display detected configuration:
š CONFIGURATION DETECTED
------------------------------------------
Monetization: Freemium (IAP/Subscriptions)
Paywall: Custom Implementation
iPad Support: iPhone Only
MCP Status:
ā Claude in Chrome: Available
ā RevenueCat MCP: Available
Checks to run:
ā Version metadata
ā IAP/Subscription validation
ā RevenueCat cross-check (via MCP)
ā Custom paywall code analysis
ā App Review information
ā Privacy configuration
ā URL validation
ā iPad screenshots (iPhone only)
If Claude in Chrome MCP is not available, stop and show error (see Prerequisites Check below).
If RevenueCat MCP is not available but user has freemium app, fall back to browser automation for RC checks.
CRITICAL: First verify Claude in Chrome MCP is available.
// Test Claude in Chrome MCP connectivity
mcp__claude-in-chrome__tabs_context_mcp({ createIfEmpty: true });
If this fails or returns an error, STOP IMMEDIATELY and display:
šØ CLAUDE IN CHROME MCP NOT AVAILABLE
The app-store-preflight command requires browser automation via Claude in Chrome.
To fix:
1. Ensure Chrome is running
2. Verify Claude in Chrome extension is installed and enabled
3. Check MCP server is connected in Claude Code settings
Cannot proceed without browser access.
If successful, you should receive a response with availableTabs and a tabGroupId. Use a tab from this group or create a new one for navigation.
After navigating to App Store Connect, verify the user is logged in:
If login screen detected, STOP and display:
š APP STORE CONNECT LOGIN REQUIRED
Please log in to App Store Connect in your browser, then run this command again.
URL: https://appstoreconnect.apple.com
Signs of login required:
appleid.apple.com or idmsa.apple.comAccept either:
https://appstoreconnect.apple.com/apps/XXXXXXXXXX/...XXXXXXXXXX (10-digit number)Extract app ID and construct base URL: https://appstoreconnect.apple.com/apps/{app_id}
Check if RevenueCat MCP is available:
// Test RevenueCat MCP connectivity
// Tool name varies - try common patterns
mcp__revenuecat__mcp_RC_get_project();
If RevenueCat MCP is available, perform cross-validation:
// Get all apps, products, offerings, and entitlements
const apps = mcp__revenuecat__mcp_RC_list_apps({ project_id: "proj..." });
const products = mcp__revenuecat__mcp_RC_list_products({ project_id: "proj..." });
const offerings = mcp__revenuecat__mcp_RC_list_offerings({ project_id: "proj..." });
const entitlements = mcp__revenuecat__mcp_RC_list_entitlements({ project_id: "proj..." });
App Store App Exists in RC
app_storeProduct ID Matching
store_identifier exists in App Store ConnectOffering Configuration
is_current: true)Entitlement Mapping
Add to output:
š REVENUECAT CROSS-CHECK
------------------------------------------
RC Project: {project_name}
iOS App: {app_name} ({bundle_id})
Products:
ASC ā RC
ā com.myapp.premium_monthly - Matched
ā com.myapp.premium_yearly - Matched
ā com.myapp.lifetime - In RC but NOT in ASC (BLOCKING)
Offerings:
ā Current offering set: "default"
ā Packages configured with products
Entitlements:
ā "Premium" linked to 2 products
Determine which paywall approach is used and validate accordingly:
Check if using RC's native paywall feature:
If NOT using RC Paywalls, search codebase for paywall implementation:
// Search for RevenueCat SDK usage patterns
Grep({
pattern: "getOfferings|useOfferings|Purchases.getOfferings",
type: "ts",
});
Grep({ pattern: "purchasePackage|purchaseProduct", type: "ts" });
Grep({ pattern: "RevenueCatUI|PaywallView|Paywall", type: "ts" }); // RC Paywalls SDK
Validate custom paywall code:
Offering Fetch
offerings.current)Package Display
package.product.priceStringintroPrice)Purchase Flow
purchasePackage() not purchaseProduct()Product ID Consistency
Common code issues:
$rc_monthly vs $rc_annual)Report format:
š³ PAYWALL IMPLEMENTATION CHECK
------------------------------------------
Type: Custom Implementation (not RC Paywalls)
Code Analysis:
Offering fetch: src/screens/PaywallScreen.tsx:45
ā Uses offerings.current
ā Handles loading state
Package display: src/components/SubscriptionOption.tsx:23
ā Uses package.product.priceString
ā ļø Trial text hardcoded - verify matches ASC config
Purchase flow: src/hooks/usePurchase.ts:67
ā Uses purchasePackage()
ā Error handling present
ā Restore purchases implemented
Product ID Cross-Check:
Code Reference RC Product ASC Product
"com.myapp.monthly" ā Found ā Found
"com.myapp.yearly" ā Found ā Found
myapp_monthly vs myapp_premium_monthly)If RevenueCat MCP is NOT available, use Chrome browser automation instead:
Navigate to RevenueCat Dashboard
https://app.revenuecat.com/projects/{project_id}/apps
Ask the user for their RevenueCat project ID if not known.
Check Authentication If login screen shown, display:
š REVENUECAT LOGIN REQUIRED
Please log in to RevenueCat in your browser, then run this command again.
URL: https://app.revenuecat.com
Browser Validation Steps
a. Navigate to Apps page - verify iOS app exists b. Navigate to Products page - screenshot and extract product IDs c. Navigate to Entitlements page - verify products attached d. Navigate to Offerings page - verify current offering set
Cross-Reference with ASC Compare product IDs found in RC browser with those in App Store Connect:
store_identifier values from RC products pageReport format (browser fallback):
š REVENUECAT CROSS-CHECK (via browser)
------------------------------------------
RC Project: MyApp (proj1a2b3c4d)
iOS App: MyApp (App Store) - com.example.myapp
Products Found:
RC Product ID ASC Match
com.myapp.premium_monthly ā Found in ASC
com.myapp.premium_yearly ā Found in ASC
Offerings:
ā "default" is current offering
Entitlements:
ā "Premium" configured
Navigate through App Store Connect sections and validate each area.
Navigate to: /distribution/ios/version/inflight
Build:
Screenshots (must show app in use, not just splash/login):
App Previews (if present):
Metadata:
What's New:
Navigate to: /distribution/subscriptions
Subscription Group Status:
For each subscription group:
Version Attachment (CRITICAL - common rejection cause):
Return to version page and scroll to "In-App Purchases and Subscriptions":
Individual Product Validation:
Click into each subscription/IAP to verify:
Subscription-Specific Requirements (Guideline 3.1.2):
Consumables & Non-Consumables:
Loot Boxes / Randomized Items (if applicable):
On version page, scroll to "App Review Information":
Contact Information:
Demo Account (CRITICAL if app requires login):
Notes for Review:
Navigate to: General > App Information
Privacy (CRITICAL):
Categorization:
Age Rating:
Content Rights:
Navigate to: App Store > App Privacy
Privacy Labels:
Navigate to: /distribution/pricing
Validate:
Test that all configured URLs are reachable using WebFetch:
// Test each URL returns 200 (not 404, 500, or redirect to error page)
WebFetch({
url: privacyPolicyUrl,
prompt: "Does this page load successfully? Return YES or NO.",
});
WebFetch({
url: supportUrl,
prompt: "Does this page load successfully? Return YES or NO.",
});
WebFetch({
url: marketingUrl,
prompt: "Does this page load successfully? Return YES or NO.",
});
URLs to validate:
Common URL issues:
Report format:
š URL VALIDATION
------------------------------------------
Privacy Policy: https://example.com/privacy
ā Reachable (200 OK)
Support URL: https://example.com/support
ā FAILED - 404 Not Found (BLOCKING)
Marketing URL: https://example.com
ā Reachable (200 OK)
Search codebase for compliance with IAP requirements:
// Check for restore purchases implementation
Grep({ pattern: "restorePurchases|restoreTransactions", type: "ts" });
// Check for privacy policy link in-app
Grep({ pattern: "privacy|privacyPolicy|privacy-policy", type: "ts" });
// Check for license key / QR code payment bypass (FORBIDDEN)
Grep({
pattern: "licenseKey|license_key|activationCode|qrCode.*payment",
type: "ts",
});
Required in Code:
If Free Trial in Paywall:
Report format:
š» CODE VERIFICATION
------------------------------------------
Restore Purchases:
ā Found: src/screens/SettingsScreen.tsx:145
ā Found: src/hooks/usePurchases.ts:78
Privacy Policy (In-App):
ā Found: src/screens/SettingsScreen.tsx:234
Links to: https://example.com/privacy
Alternative Payment Bypass:
ā No forbidden patterns found
Trial Configuration:
ā ļø Trial text in code says "7 days" - verify matches ASC config
Generate a structured report:
============================================
APP STORE CONNECT PRE-FLIGHT CHECK
============================================
App: {app_name}
Version: {version}
Date: {date}
š« BLOCKING ISSUES (will cause rejection)
------------------------------------------
- [Area]: [Specific issue]
- [Area]: [Specific issue]
ā ļø WARNINGS (may cause rejection or delays)
------------------------------------------
- [Area]: [Issue description]
ā
PASSED CHECKS
------------------------------------------
- Version metadata complete
- Build uploaded and selected
- [etc.]
š MANUAL VERIFICATION NEEDED
------------------------------------------
- Demo account credentials work
- Screenshots accurately represent app
- [etc.]
============================================
RECOMMENDATION: [Ready to submit / Fix X issues first]
============================================
Based on Apple's App Review Guidelines:
haiku model for fast validationIf a section fails to load or is inaccessible:
If logged out: