Why RevenueCat instead of raw StoreKit

Apple's StoreKit framework gives you direct access to the App Store's purchase infrastructure, but it doesn't solve the hard parts: server-side receipt validation, cross-device entitlement state, webhook delivery for subscription events, analytics on trial conversions and churn. RevenueCat handles all of that — it's a thin SDK on the client and a backend that stays synced with Apple's servers.

The other reason to use RevenueCat: the Paywalls feature. Rather than building a custom UIKit or SwiftUI paywall from scratch (and rebuilding it every time you want to run a pricing experiment), RevenueCat lets you design, iterate, and A/B test your paywall UI from the dashboard without a code deploy. For a solo developer, this is the single biggest time saving.

RevenueCat's SDK is free up to $2,500 monthly tracked revenue, then 1% of revenue above that. For most indie apps, the cost is zero until you're well into paying customers.

Step 1: Create products in App Store Connect

Before touching RevenueCat, your subscription products need to exist in App Store Connect. RevenueCat fetches product metadata from Apple — it can't invent products that don't exist there first.

In App Store Connect, navigate to your app → Monetization → Subscriptions. Create a subscription group (e.g. "Pro Access"), then add the individual products:

Note: products won't be purchasable until your banking information is on file in App Store Connect and your app has been submitted at least once (even to TestFlight). For sandbox testing, this restriction doesn't apply.

Step 2: Install the RevenueCat SDK

The RevenueCat iOS SDK (called purchases-ios) installs via Swift Package Manager:

  1. In Xcode: File → Add Package Dependencies
  2. Enter: https://github.com/RevenueCat/purchases-ios-spm
  3. Set the version rule to "Up to Next Major" from the current release
  4. Add both RevenueCat and RevenueCatUI to your app target — RevenueCatUI contains the Paywalls components

If you're in a Flutter project, the package is purchases_flutter on pub.dev — the concepts are identical but the API surface is slightly different. See our post on Flutter subscriptions with RevenueCat for the Flutter-specific flow.

Step 3: Configure the SDK

Initialize RevenueCat once at app startup — in your @main App struct or AppDelegate:

import RevenueCat

@main
struct MyApp: App {
    init() {
        Purchases.configure(withAPIKey: "appl_xxxxxxxxxxxxxxxx")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The API key is in your RevenueCat dashboard under Project Settings → API Keys. Use the iOS Public SDK key (starts with appl_). Never use the secret key in client code.

User identity: By default, RevenueCat assigns an anonymous ID per device. If your app has its own user accounts, call Purchases.shared.logIn(yourUserId) after the user authenticates — this links purchases to your user ID and enables cross-device entitlement syncing.

Step 4: Create an Offering in the RevenueCat dashboard

RevenueCat's Offerings are the configuration layer that maps your App Store products to what gets displayed on your paywall. This separation is what enables you to change pricing, add a product, or run an experiment without a code deploy.

In the RevenueCat dashboard:

  1. Go to your project → Products. Add each product by entering its App Store product ID exactly as you created it in App Store Connect.
  2. Go to Monetization → Offerings → Create a new Offering. Name it default (the SDK fetches the current offering by this identifier by default).
  3. Inside the offering, create a Package for each purchase option: a Monthly package containing pro_monthly, an Annual package containing pro_annual. RevenueCat uses standard package identifiers ($rc_monthly, $rc_annual) that the Paywalls builder understands automatically.

Step 5: Build your paywall with RevenueCat Paywalls

RevenueCat Paywalls is a no-code UI builder for your paywall screen. You design it in the dashboard — layout, colors, headline copy, CTA text — and the SDK renders it natively in your app. To add it:

import RevenueCatUI
import RevenueCat

struct UpgradeView: View {
    @State private var isPresentingPaywall = false

    var body: some View {
        Button("Upgrade to Pro") {
            isPresentingPaywall = true
        }
        .presentPaywallIfNeeded(
            requiredEntitlementIdentifier: "pro",
            purchaseCompleted: { customerInfo in
                print("Purchase completed:", customerInfo.entitlements)
            },
            restoreCompleted: { customerInfo in
                // handle restore
            }
        )
    }
}

The presentPaywallIfNeeded modifier checks whether the user already has the entitlement — if they do, it does nothing; if they don't, it presents the paywall. The UI it renders is pulled from your RevenueCat dashboard configuration. No layout code required in your app.

Alternatively, if you want full control over when the paywall appears (e.g. as a full-screen modal rather than a conditional overlay), use PaywallView() directly as a SwiftUI view inside a sheet or fullScreenCover.

Step 6: Check entitlements to gate features

After a purchase, RevenueCat's SDK updates the customer's entitlements. Gate features by checking them — don't store a local boolean. RevenueCat's backend stays in sync with Apple's subscription state, so a canceled subscription will automatically reflect as inactive even without the user doing anything in your app.

func checkProAccess() async -> Bool {
    do {
        let customerInfo = try await Purchases.shared.customerInfo()
        return customerInfo.entitlements["pro"]?.isActive == true
    } catch {
        return false
    }
}

For more on managing subscription state across sessions — including handling grace periods, billing retry, and the customerInfo update flow — see our post on RevenueCat restore purchases and subscription state.

Step 7: Placement — where and when to show the paywall

When you show the paywall matters as much as what's on it. Three patterns that convert well:

On feature gate

The user taps a pro feature — export, a premium widget, unlocking a higher limit — and the paywall appears immediately after that action. This is the highest-converting placement because the user just demonstrated intent: they wanted something and hit a wall. The paywall shows at the moment motivation is highest.

On a natural completion moment

After the user completes a meaningful action for the first time — finishes setting up a project, logs their first entry, completes onboarding — you have a brief window where they feel good about the app. Showing the paywall at this moment, with a value summary ("you've done X, unlock Y to get Z"), converts better than showing it on launch.

On a usage milestone

After the user has used the app N times (e.g. 5 sessions, or used the free feature 3 times), show a soft prompt: "You've been using [App] for a week — here's what Pro unlocks." This pattern respects the user's trial period and converts users who have experienced the core value loop.

What doesn't work: showing the paywall immediately on first launch. Users who haven't experienced any value yet have no reason to pay. Apple's guidelines also discourage paywalls that block access before any free trial or usage — it's a common rejection reason under guideline 3.1.1.

What not to do

Avoid showing the paywall more than once per session after a dismiss. Repeated paywalls train users to dismiss without reading. One impression per session on dismissal; let the user discover the paywall again naturally when they hit a gate.

Step 8: Test in the StoreKit sandbox

All purchase testing happens in Apple's sandbox environment before production. Set up a sandbox tester in App Store Connect:

  1. App Store Connect → Users and Access → Sandbox → Testers
  2. Add a tester with a new email address (it doesn't need to be a real inbox)
  3. On your physical device (sandbox doesn't work in Simulator), sign out of your personal Apple ID in Settings → App Store
  4. Run your app. When prompted for an Apple ID during purchase, sign in with the sandbox tester account

Sandbox subscriptions renew on a compressed schedule: a monthly subscription renews every 5 minutes, an annual every 1 hour. This lets you test renewal, expiration, and billing retry flows quickly. RevenueCat's dashboard shows sandbox transactions in real time — you can watch entitlement state update as test renewals fire.

StoreKit testing in Xcode: Xcode 14+ includes a StoreKit test environment for unit testing purchase flows without a network connection. Create a .storekit configuration file in your project and set it as the StoreKit Configuration in your scheme's Run settings. This is the fastest way to iterate on paywall logic.

What the paywall drives traffic to

Your paywall converts users who are already in your app. But the users who reach your paywall are users who installed your app — and install rates are driven by your App Store listing. A well-implemented paywall with a weak App Store listing means you're leaving conversions on the table at the top of the funnel.

The screenshot set is the highest-leverage part of your App Store listing for driving installs: it's what users see in search results before they tap through. ezscreenshots makes it fast to create a consistent, polished screenshot set — drop in your Simulator exports, add outcome-focused captions, export at the correct dimensions for every device size. The same users your paywall converts are the users your screenshots need to attract first.

For the App Store listing side — dimensions, caption placement, device frames — see the App Store screenshot template guide.

App Store screenshot for a subscription app showing outcome-focused caption in ezscreenshots editor
The paywall converts users already in your app. The screenshots convert users still in App Store search results. Both need to work for your monetization to compound.

Screenshots that convert the traffic your paywall depends on

A working RevenueCat paywall only converts users who install first. ezscreenshots makes it fast to create the screenshot set that drives those installs — drop in your Simulator exports, add outcome-focused captions, export at the right dimensions. Free, no account needed.

Try ezscreenshots →

Summary