โ† All articles

How to Add a Free Trial to an iOS Subscription

A free trial on an iOS subscription is an App Store introductory offer. Here is how to set one up, show it in your paywall, and test it before you ship.

How to Add a Free Trial to an iOS Subscription

A free trial on an iOS subscription is not a separate feature you build. It is an App Store introductory offer that you attach to an existing subscription product in App Store Connect, and the system handles the rest: it charges nothing for the trial length, then converts the customer to the paid price automatically unless they cancel. Your app's only job is to detect who is eligible and show the trial clearly on the paywall.

This post covers what a free trial actually is at the App Store level, the eligibility rule that trips up most indie developers, how to configure it in App Store Connect, and how to read and display it from both StoreKit 2 and RevenueCat. It finishes with how to test the trial locally before you ship, and the one paywall disclosure that gets apps rejected.

On this page

A free trial is an introductory offer

App Store subscriptions support three kinds of introductory offer, and a free trial is one of them:

  • Free trial. The customer pays nothing for a set period (for example one week or one month), then renews at the standard price.
  • Pay as you go. A reduced price for a number of billing periods (for example 3 months at half price).
  • Pay up front. A single reduced payment for a longer period (for example one year at a discount), then standard renewal.

All three are configured the same way in App Store Connect and surfaced through the same StoreKit APIs. A free trial is simply an introductory offer whose payment mode is "free." Because it is a property of the subscription product and not of your app binary, you can add, change, or remove a trial without shipping an update. If you want the full lifecycle context first, see how an iOS subscription works end to end.

Who is eligible for the trial

This is the rule that surprises people: an introductory offer is for new subscribers only. A customer is eligible for the free trial on a subscription only if neither they nor anyone in their Family Sharing group has previously used an introductory offer for any product in that subscription's group.

The consequences matter for your funnel:

  • Someone who already had the trial and cancelled does not get it again. If they resubscribe, they pay full price from day one.
  • Eligibility is per subscription group, not per product. If you offer monthly and yearly in the same group and a customer trials the monthly, they are no longer eligible for a trial on the yearly.
  • Because eligibility is per Apple ID and Family Sharing group, you cannot assume every visitor sees a trial. Your paywall has to ask StoreKit or RevenueCat and render accordingly, rather than hard-coding "7 days free" in a text label.

Getting this wrong produces a real bug: a paywall that promises a free trial to a returning customer who is charged immediately, which leads to refund requests and one-star reviews.

Setting it up in App Store Connect

You attach the trial to a subscription that already exists. In App Store Connect, open the subscription under your app, find its introductory offer section, and create a new offer:

  1. Type: Free.
  2. Territories: all, or a specific subset. You can run a trial only in the markets where it makes sense.
  3. Duration: one value from Apple's fixed set (common choices are 3 days, 1 week, and 1 month).
  4. Start and end dates: either a fixed window or no end date so the trial runs indefinitely.

A subscription can have one introductory offer active at a time, though you can schedule offers back to back with dates. You do not set a "trial price," because a free trial has no price by definition. The one thing to line up first is the subscription itself: the product ID, duration, group, and its regional prices all have to exist before an offer can hang off them. If you are still deciding what those prices should be, the 25-country pricing guide covers setting a base price and adapting it per market.

Showing the trial in StoreKit 2

With StoreKit 2 you read the offer off the product's subscription info and check eligibility asynchronously. Only build the trial label when the current customer is actually eligible:

import StoreKit
 
/// A human-readable trial string, or nil if this customer won't see a trial.
func freeTrialLabel(for product: Product) async -> String? {
    guard let subscription = product.subscription,
          let offer = subscription.introductoryOffer,
          offer.paymentMode == .freeTrial,
          await subscription.isEligibleForIntroOffer
    else {
        return nil
    }
 
    let unit: String
    switch offer.period.unit {
    case .day:   unit = "day"
    case .week:  unit = "week"
    case .month: unit = "month"
    case .year:  unit = "year"
    @unknown default: unit = "period"
    }
 
    let count = offer.period.value
    let plural = count == 1 ? unit : "\(unit)s"
    return "\(count) \(plural) free, then \(product.displayPrice)"
}

Two details do the heavy lifting. introductoryOffer is nil when no offer is configured, and paymentMode == .freeTrial distinguishes a trial from the pay-as-you-go and pay-up-front modes. The await subscription.isEligibleForIntroOffer check is what keeps you from promising a trial to a returning subscriber. Because it is async, load it once when the paywall appears and store the result in your view state rather than calling it in a body recomputation.

Showing the trial with RevenueCat

If you use RevenueCat, the eligibility check and the offer live on the StoreProduct, and RevenueCat gives you a batch eligibility call:

import RevenueCat
 
let offerings = try await Purchases.shared.offerings()
guard let package = offerings.current?.availablePackages.first else { return }
let product = package.storeProduct
 
let eligibility = await Purchases.shared.checkTrialOrIntroDiscountEligibility(
    productIdentifiers: [product.productIdentifier]
)
 
if eligibility[product.productIdentifier]?.status == .eligible,
   let intro = product.introductoryDiscount,
   intro.paymentMode == .freeTrial {
    let period = intro.subscriptionPeriod
    print("Show: \(period.value) \(period.unit) free trial")
}

In practice you rarely need to hand-render this. RevenueCat's prebuilt PaywallView (from RevenueCatUI) reads the introductory offer and the customer's eligibility for you and shows the correct trial copy automatically, including hiding it from ineligible customers. Reach for checkTrialOrIntroDiscountEligibility only when you are building a custom paywall and need the eligibility status yourself. The mechanics of wiring RevenueCat into a SwiftUI app are covered in adding subscriptions with RevenueCat.

The paywall disclosure Apple requires

App Review guideline 3.1.2 requires your paywall to state the terms of a subscription clearly and honestly, and free trials are where reviewers look hardest. Before the purchase button, the customer needs to see, in plain language:

  • The subscription name and the length of each billing period.
  • The trial length and, explicitly, the price they will be charged when it ends.
  • That the subscription renews automatically until cancelled.
  • Working links to your Terms of Use and Privacy Policy.

The specific pattern that gets rejected is a big "Start your free trial" button with the "then $X per month" buried or missing. Put the "free for N days, then $X per period, auto-renews" line next to the button in text the reviewer cannot miss. Both StoreKit's own paywall components and RevenueCat's PaywallView render this correctly by default, which is one more reason to lean on them rather than a bespoke layout that a reviewer flags.

Testing the trial before you ship

You do not need App Store Connect approval to test a trial. A StoreKit configuration file lets you add an introductory offer to a subscription and exercise the whole flow in the simulator:

  1. In the .storekit file, select the subscription and add an introductory offer of type "Free Trial" with a duration.
  2. Run the app. Eligible-customer logic, the trial label, and the purchase flow all work against the local configuration, with no network round trip to Apple.
  3. To re-test the ineligible path, open the transaction manager (Debug > StoreKit in Xcode, or the Transactions inspector) and delete the trial transaction to reset eligibility, or toggle it back on to simulate a returning subscriber.

For an end-to-end check against Apple's servers, use a Sandbox Apple ID. Sandbox accelerates renewal timelines (a one-week trial elapses in minutes) so you can watch the trial convert to a paid renewal without waiting real calendar time. Test both eligible and ineligible customers before you ship, because the ineligible path is the one that produces surprise charges in production.

FAQ

How long can a free trial be?

Apple offers a fixed set of durations, from 3 days up to 1 year. The common indie choices are 3 days, 1 week, and 1 month. Longer trials lower the barrier to starting but delay revenue and can raise the share of customers who forget and later request a refund.

Do free trials work without RevenueCat?

Yes. A free trial is an App Store introductory offer configured entirely in App Store Connect, and StoreKit 2 exposes it directly through Product.SubscriptionInfo. RevenueCat is a convenience layer that reads the same offer and handles eligibility and paywall display for you. It does not create the trial.

Why does my paywall show a trial to some users and not others?

Because introductory offers are new-subscriber only. Anyone who has already used an introductory offer for a product in that subscription group, including through Family Sharing, is not eligible and should be charged full price. Always gate the trial label on the eligibility check rather than hard-coding it.

Can I add a free trial to a subscription that is already live?

Yes. The offer is a property of the subscription product, not of your app binary, so you can add or remove a trial in App Store Connect without submitting an app update. Existing subscribers are unaffected.

Does the customer get charged when the trial ends?

Yes, automatically, at the standard subscription price, unless they cancel before the trial period ends. This auto-renewal is exactly why guideline 3.1.2 requires you to disclose the post-trial price on the paywall.

Can someone use the free trial more than once?

Not on the same subscription group. Eligibility is tracked per Apple ID and Family Sharing group, so a customer gets one introductory offer per group. If they cancel and resubscribe later, they pay full price from the start.


A free trial is a small step once the subscription underneath it is set up correctly, and setting that subscription up is most of the actual work: the product, its group and product ID, its price in every market, and a paywall that reads eligibility and discloses terms the way review expects. Spaceport does that part for you. It creates and prices your subscription products in App Store Connect across 25 major markets through the App Store Connect API, matches them in RevenueCat, and generates a SwiftUI project with the RevenueCat paywall and a StoreKit configuration file already wired in. Adding a free trial then becomes one configuration step in App Store Connect on products that already exist, tested against a config file that is already in the project. And when you are ready to line up a waitlist and a launch-day audience for the app, our sister tool Lighthouse handles that side.

From an indie iOS dev, for indie iOS devs.

Read more at spaceport.build

Community appsJoin Discord