SwiftUI Guide

How to Add a Paywall to a SwiftUI App

A hand-rolled RevenueCat paywall in SwiftUI — fetch offerings, wire the purchase and restore flow, and gate your premium feature behind an entitlement. Or skip the plumbing with The Swift Kit.

Last updated: 2026-06-09 8 min read By Ahmed Gagan, iOS Engineer
Quick Answer

To add a paywall to a SwiftUI app, integrate the RevenueCat Purchases SDK, configure it on launch with your public API key, fetch the current offering, and render its packages in a custom SwiftUI view. Wire each package to Purchases.shared.purchase(package:), handle the user-cancelled case, add a Restore Purchases button, and unlock content by checking customerInfo.entitlements for your active entitlement. The plumbing is roughly a day of work to build cleanly yourself, or it ships pre-wired in The Swift Kit's RevenueCat module.

Build time (hand-rolled)
~1 day for a clean, tested flow
SDK
RevenueCat Purchases (Swift Package Manager)
Most common bug
Not handling result.userCancelled

Why hand-roll it at all?

Building the paywall yourself is the best way to actually understand the RevenueCat model — offerings, packages, entitlements, and the customerInfo object that is the single source of truth for who paid. You will hit the real edge cases: a purchase that succeeds but the entitlement reads stale, the userCancelled result that looks like an error, restores that need to work before login, and prices that must come from localizedPriceString rather than hardcoded strings. None of that is hard once you have seen it. The cost is time and the maintenance of glue code you now own forever — config lifecycle, error states, loading states, and keeping the SDK current across major versions.

  • Offerings let you change pricing remotely without an app update
  • Entitlements (not product IDs) are what you gate features on
  • customerInfo updates can arrive asynchronously — subscribe to them
  • Always read prices from the SDK so currencies and intro offers stay correct

The honest trade-off vs. using the kit

If your goal is to learn StoreKit and RevenueCat deeply, hand-roll it — this guide is enough to get a working, App Store-compliant paywall. If your goal is to ship a monetized app this week, re-implementing the same purchase-and-restore plumbing every project is wasted motion. The Swift Kit's RevenueCat module is that plumbing, already wired into a centralized DesignSystem so the paywall matches your app's theme in one file, with the API key proxied through config rather than scattered in source. It is a $99 one-time purchase with lifetime updates, so SDK major-version bumps are maintained for you. The trade is the usual one: own the code and the upkeep, or buy a tested baseline and spend your time on the product.

Hand-roll a RevenueCat paywall in SwiftUI

This builds a paywall from scratch with the RevenueCat Purchases SDK — no RevenueCatUI, no templates, so you understand every line. If you would rather not own the boilerplate, the last step shows the shortcut.

  1. 1

    Set up products in App Store Connect and RevenueCat

    Create your subscription products (e.g. monthly, annual) in App Store Connect, then add them to a RevenueCat project. Create an entitlement called pro and attach it to those products, then group them under an offering called default. The offering is what your app fetches at runtime, so you can swap pricing without an app update.

  2. 2

    Install the Purchases SDK

    Add RevenueCat via Swift Package Manager. Point Xcode at the GitHub repo and add the Purchases product to your app target.

    // File > Add Package Dependencies
    // https://github.com/RevenueCat/purchases-ios
    import RevenueCat
  3. 3

    Configure RevenueCat on app launch

    Configure the SDK once, as early as possible, with your public API key. Keep the key out of source control — in The Swift Kit it lives in the centralized config, never hard-coded inline.

    @main
    struct MyApp: App {
        init() {
            Purchases.logLevel = .debug
            Purchases.configure(withAPIKey: "appl_YOUR_PUBLIC_KEY")
        }
        var body: some Scene { WindowGroup { ContentView() } }
    }
  4. 4

    Fetch the offering and render packages

    Load the current offering and lay out its packages in a SwiftUI list. Each package carries a localizedPriceString, so prices, currencies, and intro offers are formatted correctly per-store without you doing math.

    @MainActor final class PaywallModel: ObservableObject {
        @Published var packages: [Package] = []
        func load() async {
            let offerings = try? await Purchases.shared.offerings()
            packages = offerings?.current?.availablePackages ?? []
        }
    }
  5. 5

    Wire the purchase flow

    On tap, call purchase(package:) and inspect the result. The most-missed case is userCancelled — treat it as a no-op, not an error, or you will throw alerts at people who simply changed their mind.

    func buy(_ package: Package) async {
        do {
            let result = try await Purchases.shared.purchase(package: package)
            if result.userCancelled { return }
            isPro = result.customerInfo.entitlements["pro"]?.isActive == true
        } catch {
            // surface a friendly retry message
        }
    }
  6. 6

    Add Restore Purchases and gate the feature

    Apple requires a visible Restore button. Call restorePurchases(), then gate premium UI on the entitlement — read customerInfo.entitlements["pro"]?.isActive and also listen for updates so a purchase on another device unlocks here too.

    // Restore
    let info = try await Purchases.shared.restorePurchases()
    isPro = info.entitlements["pro"]?.isActive == true
    
    // Gate
    if isPro { ProFeatureView() } else { PaywallView() }
  7. 7

    Or skip the plumbing with The Swift Kit

    Everything above — config, offerings fetch, purchase and restore flow, the userCancelled trap, entitlement gating, and a themeable paywall screen — is pre-wired in The Swift Kit's RevenueCat module. You run ./setup.sh, drop in your API key, flip the feature flag, and ship. Use it as a reference build or as your starting point.

Frequently Asked Questions

Do I need RevenueCatUI to add a paywall, or can I build my own?
You can absolutely build your own. This guide hand-rolls the paywall using only the core Purchases SDK — you fetch the offering's packages and lay them out in plain SwiftUI. RevenueCatUI's PaywallView is a convenience for dashboard-configured paywalls, but it is optional. A custom view gives you full control over layout and matches a custom design system more cleanly.
Why does my SwiftUI paywall show no products?
Empty offerings is almost always a configuration gap, not a code bug. Check that your products are Approved or in Ready to Submit in App Store Connect, that they are attached to an offering in RevenueCat, that your bundle ID and StoreKit agreements are correct, and that you are testing on a sandbox account or a StoreKit configuration file. The SDK returns an empty availablePackages array rather than throwing when the store has nothing to give it.
How do I unlock the feature after purchase?
Do not gate on a product ID. After purchase or restore, read result.customerInfo.entitlements["pro"]?.isActive — the entitlement is the source of truth. Store that boolean in your app state and also subscribe to customerInfo updates so a purchase made on another device, or a restore, unlocks the feature without a relaunch.
Do I have to add a Restore Purchases button?
Yes. Apple's review guidelines require a visible way to restore previous purchases, and apps get rejected for omitting it. Add a button that calls Purchases.shared.restorePurchases() and then re-checks the entitlement. It is a few lines, but skipping it is one of the most common reasons a paywall fails review.
Is it faster to use The Swift Kit instead of building this myself?
For shipping, yes. The hand-rolled version here is roughly a day of careful work plus ongoing maintenance of the glue code. The Swift Kit ships the same RevenueCat flow pre-wired — purchase, restore, the userCancelled trap, and entitlement gating — behind a feature flag and themed by the central design system. Build it yourself to learn, or use the kit to skip straight to selling.

Keep exploring

Skip the paywall plumbing

The Swift Kit ships the full RevenueCat flow — offerings, purchase, restore, and entitlement gating — pre-wired and themed by a central design system. $99 one-time, lifetime updates, 14-day refund. Build it yourself to learn, or start here to ship this week.

Get The Swift Kit — $99

One-time purchase · Lifetime updates · 14-day refund