LimitedRound 2 is open. Get your first month free, no extra charge.Join the waitlist ›
Wire in your backend

How to add subscriptions to an iOS app

TL;DR. Auto-renewable subscriptions on iOS go through StoreKit. You create a subscription group in App Store Connect, load the products, run the purchase, and check current entitlements to know who has access. AppFlight can build the paywall and gate features, and tools like RevenueCat manage entitlements for you.

What subscriptions do for an iOS app

A subscription gives recurring access to content or features and renews automatically until the user cancels. Apple manages the billing, renewals, grace periods, and cancellations, and takes its commission. You define a subscription group with one or more tiers, then your app sells those tiers and checks whether the current Apple Account holds an active entitlement. For digital content, Apple requires this through StoreKit under guideline 3.1.1.

Adding it with AppFlight, and manually

In AppFlight you describe the plan, for example a monthly and a yearly Pro tier, and the AI builds the paywall, runs the purchase, and gates the gated screens behind an active entitlement. You create the subscription group and prices in App Store Connect, and AppFlight references them by product ID. If you connect RevenueCat or Superwall, AppFlight keeps the public key as a client key and wires entitlement checks or a remote paywall through that SDK.

Manually, you create the subscription group in App Store Connect, load the products with Product.products(for:), call purchase(), and read Transaction.currentEntitlements at launch and on Transaction.updates to keep access in sync.

A SwiftUI example

Load subscription products and check the current entitlement:

import StoreKit
import SwiftUI

@Observable
final class SubStore {
    var isSubscribed = false

    func refresh() async {
        for await result in Transaction.currentEntitlements {
            if case .verified(let transaction) = result,
               transaction.productID == "com.example.pro.monthly" {
                isSubscribed = transaction.revocationDate == nil
            }
        }
    }

    func subscribe() async {
        guard let product = try? await Product.products(
            for: ["com.example.pro.monthly"]).first else { return }
        _ = try? await product.purchase()
        await refresh()
    }
}

Common pitfalls

Not handling Transaction.updates means a renewal, cancellation, or refund processed by Apple never updates the app until the next manual check. Treating a one-time check as permanent ignores that subscriptions expire, so re-check entitlements at launch. Skipping a restore-purchases option breaks access for users on a new device and can fail App Review. If managing entitlements and analytics by hand feels heavy, RevenueCat handles that layer on top of StoreKit, and Superwall lets you change the paywall remotely without shipping an update.

FAQ

Do subscriptions have to use Apple in-app purchase?

For digital content used inside the app, yes, under guideline 3.1.1. Apple handles billing, renewals, and cancellations. Stripe and other processors are only for physical goods and services delivered outside the app.

How do I know if a user is currently subscribed?

In StoreKit 2 you check Transaction.currentEntitlements, which returns the active entitlements for the signed-in Apple Account. If a transaction for your subscription product is present and not expired, the user has access.

Do I need RevenueCat for subscriptions?

No. StoreKit 2 alone can run subscriptions and check entitlements. RevenueCat is worth it when you want cross-platform access, server-side validation, and revenue analytics without building that layer yourself.

Sources

Build this app without opening Xcode.

AppFlight turns a plain-English prompt into a real native iOS app and ships it to the App Store. Round 2 is open: free for your first month.

Join the waitlist