Most of the hard work of shipping an iOS app is not the code. The build compiles, the simulator looks great, and then you hit App Store Connect and discover an entire second product: a review process, a metadata system, and a set of policies that can hold your launch for days. This is the field guide we wish we'd had — the non-obvious parts that decide whether you ship on schedule or spend a week trading rejection notices with Apple.
We build a fair number of these at CodeAustral, several of them in restaurant tech, where an app might handle reservations, loyalty, mobile ordering, and payments all at once. That mix touches almost every sharp edge Apple has, so the examples below lean that way. The principles apply to any app.
TestFlight Is Your Real Staging Environment
The single biggest mistake teams make is treating TestFlight as an afterthought a few days before launch. It is your staging environment, and it has its own review queue.
There are two distinct tiers, and they behave differently:
- Internal testing — up to 100 testers who are members of your App Store Connect team (specifically users with the right role). Builds are available almost immediately after processing, with no review. This is your fast loop.
- External testing — up to 10,000 testers via email or a public link. The first build of an app, and builds with significant changes, require TestFlight Beta App Review, which is lighter than full review but still a gate that can take a day.
The non-obvious parts:
- Build processing is asynchronous. After upload, a build sits in "Processing" for anywhere from a few minutes to over an hour. Don't schedule a release the same hour you upload.
- Each build expires after 90 days. Long betas need re-uploads.
- You must provide a beta test information sheet (what to test, demo account credentials) or external review gets rejected.
- Crash logs and tester feedback flow back into App Store Connect — wire up your team to actually read them.
A practical rule we follow: the build you submit for App Store review should be a build that has already lived in external TestFlight for at least a few days with real testers. If you're submitting a build nobody has run, you're using your customers as your QA team.
Review Guideline Pitfalls That Actually Get Apps Rejected
Apple's App Review Guidelines are long, but rejections cluster around a small set of issues. In our experience these are the repeat offenders:
- Guideline 2.1 — incomplete information. The reviewer can't get past your login wall. Always provide a working demo account in App Review notes, and make sure it doesn't expire. For restaurant apps, this means a demo restaurant with seeded data so the reviewer sees a real menu, not an empty state.
- Guideline 4.2 — minimum functionality. A thin wrapper around a website gets rejected. If your app is mostly a web view, it needs native value: push notifications, offline behavior, hardware integration, real native navigation.
- Guideline 3.1.1 — in-app purchase. Selling digital goods or unlocking app features with any payment method other than Apple's IAP. More on this below — it's the costliest mistake.
- Guideline 5.1.1 — data collection and permissions. Requesting permissions you don't visibly use, or forcing account creation before the user gets any value. Apple now requires that if your app supports account creation, it must also support account deletion from inside the app.
- Guideline 4.0 — design. Broken layouts on the current device sizes, placeholder content, or buttons that lead nowhere.
The pattern: most rejections are about the reviewer's *experience of your app*, not your code quality. Reviewers are humans on a clock. Remove every reason for them to get stuck.
Write App Review notes like a runbook
A short, specific notes field prevents most "we couldn't proceed" rejections:
Demo account:
email: [email protected]
password: AppReview2026
Notes:
- Tap "Order" tab to see the full mobile-ordering flow.
- IAP "Pro Loyalty" unlocks under Profile > Upgrade (use Sandbox tester).
- Location permission is used for the "Near me" restaurant list only.
- Account deletion: Profile > Settings > Delete account.Privacy Nutrition Labels and the Permission Story
Since iOS introduced privacy "nutrition labels," your App Store listing must declare every category of data you collect and how it's used — and it has to match your actual behavior. Apple cross-checks declarations against the frameworks your binary links and the network calls SDKs make. A mismatch is both a rejection risk and a trust problem.
Two things people miss:
- Third-party SDKs collect data on your behalf. Your analytics, crash reporter, and ad SDKs all count. You are responsible for declaring what they collect.
- Required Reason APIs and Privacy Manifests. Apple now requires a privacy manifest (
PrivacyInfo.xcprivacy) for your app and for many common SDKs, declaring data types and the reason codes for using certain APIs (file timestamps, user defaults, disk space, system boot time). If a flagged SDK lacks a manifest and signature, your upload can be rejected at submission time.
A minimal privacy manifest looks like this:
{
"NSPrivacyTracking": false,
"NSPrivacyCollectedDataTypes": [
{
"NSPrivacyCollectedDataType": "NSPrivacyCollectedDataTypeEmailAddress",
"NSPrivacyCollectedDataTypeLinked": true,
"NSPrivacyCollectedDataTypeTracking": false,
"NSPrivacyCollectedDataTypePurposes": [
"NSPrivacyCollectedDataTypePurposeAppFunctionality"
]
}
],
"NSPrivacyAccessedAPITypes": [
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
"NSPrivacyAccessedAPITypeReasons": ["CA92.1"]
}
]
}Separately, every permission prompt needs a clear usage string in your Info.plist (for example NSLocationWhenInUseUsageDescription). Write these in plain language tied to a user benefit — "to show restaurants near you" — not "to access location services." Vague strings get flagged.
In-App Purchase Rules: The Most Expensive Thing to Get Wrong
This is where teams lose the most time and, occasionally, the most money. The core rule: if you sell digital content or unlock functionality inside the app, you must use Apple's In-App Purchase, and Apple takes its commission (15% under the Small Business Program for most developers under the revenue threshold, otherwise 30%; 15% on auto-renewing subscriptions after the first year).
What you can and cannot do:
| Scenario | Allowed payment method |
|---|---|
| Unlock a premium feature / digital subscription | Apple IAP (required) |
| Consumable credits used inside the app | Apple IAP (required) |
| Physical goods or real-world services (food delivery, a dine-in meal, a booked table) | Your own processor (Stripe, etc.) — IAP not allowed |
| "Reader" content bought elsewhere | IAP optional, with conditions |
For restaurant tech this distinction is the whole game. Ordering a meal or paying a restaurant bill is a real-world good or service — you use Stripe or your own gateway and Apple takes nothing. But selling a "Pro" loyalty tier, ad-free experience, or premium menu-analytics dashboard *inside the app* is digital and must go through IAP. Mixing these up in one binary is common and gets rejected.
A few rules that trip teams up:
- You generally cannot link out to your website to buy a digital subscription that the app then unlocks (anti-steering rules have loosened in some regions following legal rulings, but treat external linking as fragile and region-specific).
- Subscriptions need a visible price, period, and a link to your terms and privacy policy on the paywall screen, plus clear renewal disclosure.
- You must verify purchases server-side, not just trust the client. Use the App Store Server API and StoreKit 2's transaction verification.
Here's the shape of a server-side verification check using the App Store Server API response, in TypeScript:
import { decodeTransaction } from "./appstore";
async function grantEntitlement(signedTransaction: string, userId: string) {
// Verify the JWS signature chain against Apple's root certs first.
const tx = await decodeTransaction(signedTransaction);
const isActive =
tx.type === "Auto-Renewable Subscription" &&
(tx.expiresDate ?? 0) > Date.now() &&
tx.revocationDate == null;
if (!isActive) return { granted: false };
// Idempotent: keyed on Apple's transactionId, never the client's word.
await db.entitlements.upsert({
where: { transactionId: tx.transactionId },
create: { userId, productId: tx.productId, transactionId: tx.transactionId },
update: { userId },
});
return { granted: true };
}Test the whole flow with Sandbox testers in App Store Connect before you submit. Sandbox subscriptions renew on an accelerated clock, which is the only sane way to test renewal and expiry logic.
ASO Basics: The Metadata Most Teams Leave on the Table
App Store Optimization is not a dark art; it's just disciplined metadata. The fields that move installs:
- App name (30 chars) and subtitle (30 chars) — the two heaviest-weighted fields for keyword ranking and the first thing a human reads. Put your strongest keyword in the name, a benefit in the subtitle.
- Keyword field (100 chars) — comma-separated, no spaces, no repeats, don't repeat words already in the name. Don't waste characters on plurals (Apple stems) or your brand name.
- Promotional text (170 chars) — editable without a new build; use it for current offers.
- Description — read by humans more than ranking algorithms; lead with the value, not a feature dump.
A useful decision list when choosing keywords:
- Pick terms a confused customer would type, not internal jargon. "Restaurant reservations," not "FOH guest pipeline."
- Prefer two or three medium-competition phrases you can plausibly rank for over one head term you can't.
- Localize per market — keyword sets are per-locale, and translating them (not just the UI) is free reach.
Screenshots, Previews, and the Listing That Converts
Most installs are decided on the first two screenshots, often before anyone reads a word. Treat them as a landing page, not a gallery of your UI.
- Lead with value, not chrome. The first screenshot should state the core benefit as a caption over a clean device frame. "Fill empty tables tonight" beats a screenshot of your settings screen.
- Required sizes. You must supply screenshots for the current 6.9" iPhone display class; iPad screenshots are required if you support iPad. Apple scales some sizes down, but design for the largest required device.
- App previews (video) are optional but convert well for interaction-heavy apps — keep them 15–30 seconds and assume they autoplay muted.
- Localize the captions. A Brazilian user scanning a Portuguese store sees more relevance in Portuguese captions; this is one of the cheapest conversion wins available.
Keep the icon simple and legible at small sizes — it renders tiny in search results and on the home screen. Busy icons lose.
The Launch Checklist
Run this before you tap Submit. It's the list we paste into every iOS project's tracker.
- App icon, all required screenshot sizes, and app preview uploaded and localized.
- App name, subtitle, keywords, description, and promotional text final and localized per market.
- Privacy nutrition labels filled and matching real behavior;
PrivacyInfo.xcprivacypresent for app and SDKs. - All
Info.plistpermission usage strings written in plain, benefit-led language. - Account deletion implemented in-app (if accounts exist).
- IAP products created, in "Ready to Submit," and tested end-to-end with Sandbox testers.
- Server-side receipt/transaction verification live and idempotent.
- App Review notes include a non-expiring demo account and a numbered walkthrough.
- Support URL and privacy policy URL live and reachable.
- Build run in external TestFlight by real testers for several days.
- Age rating, export compliance (encryption) answers, and content rights confirmed.
- Release set to manual release so you control the go-live moment after approval.
That last point matters: choose manual release. Automatic release the instant Apple approves has a way of going live at 2 a.m. on a Saturday when no one is watching the support inbox.
Frequently Asked Questions
How long does App Store review take in 2026?
Most reviews complete within 24 to 48 hours, and a large majority within a day. Plan for longer around major OS launches and holidays, when queues lengthen. Expedited review is available for genuine emergencies like a critical bug fix or a time-sensitive event, but use it sparingly — it's not a planning tool.
Do I need In-App Purchase for a restaurant ordering app?
No. Ordering food, paying a bill, or booking a table is a real-world good or service, so you use your own payment processor like Stripe and Apple takes no commission. Apple's In-App Purchase is only required when you sell digital content or unlock in-app features, such as a premium loyalty subscription.
What is a privacy manifest and is it mandatory?
A privacy manifest (PrivacyInfo.xcprivacy) is a file declaring the data types your app collects and the reason codes for using certain "required reason" APIs. It is mandatory for your app and for many common third-party SDKs. Uploads that include a flagged SDK without a valid, signed manifest can be rejected at submission.
Can I use TestFlight builds for my public launch?
No. TestFlight is for beta testing only; builds expire after 90 days and aren't a substitute for an App Store release. Use TestFlight to validate the exact build you intend to ship, then submit that same build through standard App Store review for public distribution.
Why was my app rejected for "incomplete information"?
This almost always means the reviewer couldn't fully use the app, usually because of a missing or broken demo account, a login wall, or a feature gated behind something they couldn't reach. Provide a working, non-expiring demo account and a short numbered walkthrough in the App Review notes to resolve it.
Working with CodeAustral
We design and ship iOS apps end to end — from the StoreKit plumbing and server-side receipt verification to the screenshots and ASO metadata that decide whether anyone installs. If you're planning a launch and want a team that has already hit these walls and knows the way through, send us a short brief at codeaustral.com/contact and we'll tell you honestly what it'll take.

