Once I fixed the empty paywall on my first App Store submission, I assumed the subscription work was done. Products loaded, prices appeared, purchases went through in the sandbox.

The next rejection was about the screen itself. The close and back controls were the problem — a user who opened the paywall couldn't reliably get back out of it.

This is Guideline 3.1.2, Subscriptions. My first reaction was that this was a nitpick about two buttons. It isn't. If the only route out of a purchase screen is to purchase, the app has trapped the user, and that's a behaviour Apple looks for deliberately. Reviewers open the paywall and try to leave it.

That reframed how I think about a paywall. It isn't only a UI screen. It's the screen with the most review requirements attached to it in the entire app, and almost none of them are about how it looks.

Dismissal comes first

The rule I'd now check before anything else: from the moment the paywall is visible, there must be an obvious, working way to leave without buying.

That means a close control that's actually tappable and actually visible — not a low-contrast glyph in a corner, not something a safe-area inset is covering on a specific device, and not a back gesture that only works on some screens. If the paywall appears during onboarding, leaving it has to land the user somewhere sensible rather than in a loop that shows the paywall again.

Test it on a real device, not just the simulator. A control that sits under the notch or behind the home indicator on hardware you don't own is the version a reviewer will find.

What has to be on the screen before the purchase

Apple's rejection for missing subscription information lists four things that must be in the binary, and the wording it uses is specific:

Title of auto-renewing subscription, which may be the same as the in-app purchase product name. Length of subscription. Price of subscription, and price per unit if appropriate. Functional links to the privacy policy and Terms of Use (EULA).

Two words in that list do the most damage in practice.

Functional. The links have to work. A link to a page that 404s, or one that opens an empty in-app browser, counts as missing. This is where my privacy policy problem came from on the same submission — the client had given me an HTML file rather than a hosted URL, and a file isn't a link. That one is also Guideline 5.1.1.

Length. A price on its own isn't enough. The billing period has to be there with it, so "$9.99" fails and "$9.99 per month" passes. The same applies to annual plans: if you show a monthly equivalent to make it look cheaper, the actual billed period still has to be explicit.

All of it needs to be visible before the user taps to buy — not behind a "see details" expansion, and not only inside the terms you're linking to.

The metadata side is separate

This part surprised me, because it isn't in the app at all.

The privacy policy URL has to be filled into the Privacy Policy field in App Store Connect. The Terms of Use link has to exist in your metadata too — either as a link in the app description, or as a custom EULA uploaded in App Store Connect. If you're using Apple's standard terms, linking to Apple's standard EULA from the description is accepted.

So the same two documents are needed twice: once as functional links on the paywall, and once in the store listing. Doing one and not the other is a round trip, and a round trip is days.

Making the real price the loudest thing

There's a further rejection that catches people who have everything above correct, and it's about emphasis rather than content.

If your paywall promotes a free trial or introductory price more prominently than the amount the user will actually be billed, Apple treats that as misleading. The billed amount has to be the most conspicuous pricing element on the screen — font, size, colour, and position all count — and the trial has to be subordinate to it.

In practice, "7 days free" as a headline with the real price in small grey text underneath is the layout that gets rejected. Something closer to "7-day free trial, then $9.99 per month" keeps the charge attached to the offer.

Pull the numbers from StoreKit

One implementation detail that prevents a whole category of this: never hardcode prices or durations in the paywall.

Read them from the products StoreKit returns. That way the amount shown is the localised, region-correct price Apple will actually charge, and it stays correct when you change pricing later. A hardcoded "$9.99" is wrong for most of the world the moment your app is available outside your own storefront, and a paywall showing a price that doesn't match what's charged is both a rejection and a support problem.

The checklist I use now

Before I submit anything with a subscription, I go through the screen once: can I leave it, is the subscription named, is the length there, is the price from StoreKit, does it read as a recurring charge, do both links open real pages, is the billed amount the most prominent number, and are the same two links in App Store Connect.

None of that is difficult. All of it is invisible if you're thinking of the paywall as a design problem. My first submission took at least eight rejections, and the ones I found most frustrating at the time were the ones like this — not because they were unreasonable, but because nothing in the process tells you they exist until you've failed them.

The empty paywall that came before this screen is Guideline 2.1. The whole run is in the eight-rejections post.