Implementing Introductory Offers (First Period Discount) in Mobile Applications
Introductory Offers is a StoreKit mechanism that allows you to offer new subscribers a reduced price or free trial period on the first billing cycle. Apple supports three types: freeTrial (free), payAsYouGo (pay for each period at a reduced price), and payUpFront (pay once for multiple periods at a reduced price). Without proper client-side implementation, users won't see the introductory offer.
Configuration in App Store Connect
Introductory Offer is created at the subscription level in App Store Connect → Subscriptions → [Subscription] → Introductory Offers. You need to specify the type, duration, and price. Important: the offer applies only to users who have never been subscribers of this subscription group. Apple verifies this on the server.
Reading the Offer via StoreKit 2
import StoreKit
// Load the product
guard let product = try? await Product.products(for: ["premium_monthly"]).first else { return }
// Check for introductory offer
if let intro = product.subscription?.introductoryOffer {
switch intro.paymentMode {
case .freeTrial:
// Display: "First 7 days free"
let days = intro.period.value // for example, 7
let unit = intro.period.unit // .day
showFreeTrialBanner(days: days)
case .payAsYouGo:
// Display: "First month for $99"
showDiscountedPriceBanner(price: intro.displayPrice, period: intro.period)
case .payUpFront:
showUpFrontBanner(price: intro.displayPrice, duration: intro.subscriptionPeriod)
@unknown default: break
}
}
Checking Eligibility — Key Point
Users see the introductory offer only if they are eligible. However, product.subscription?.introductoryOffer doesn't directly report eligibility — the offer is present in the object regardless of whether the specific user has the right to it.
Eligibility check via StoreKit 2:
// Check subscription status via Transaction
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result {
if transaction.productID == "premium_monthly" {
// User has been a subscriber before — don't show the offer
userHasBeenSubscriber = true
}
}
}
Alternative: server-side check via App Store Server API (/inApps/v1/subscriptions/{originalTransactionId}): the server returns isInBillingRetryPeriod and full transaction history, which allows you to determine precisely whether the user has used the introductory offer.
Don't rely solely on client-side checks for business logic. Golden rule: UI decision on showing the offer on the client, validation of offer eligibility — on the server via App Store Server API or RevenueCat.
Displaying in Paywall UI
Typical scenario: the same paywall page shows different options depending on eligibility:
struct PaywallView: View {
let product: Product
var body: some View {
VStack {
if let intro = product.subscription?.introductoryOffer,
isEligibleForIntro {
IntroOfferBanner(offer: intro)
.transition(.opacity)
}
SubscriptionButton(product: product)
}
}
}
isEligibleForIntro is a @State or @Published property that is set after an asynchronous transaction check.
Using RevenueCat
If the project already uses RevenueCat, eligibility checking is significantly simpler:
Purchases.shared.getOfferings { offerings, error in
if let intro = offerings?.current?.monthly?.product.introductoryDiscount {
// RevenueCat checks eligibility via StoreKit itself
Purchases.shared.checkTrialOrIntroductoryPriceEligibility(
productIdentifiers: ["premium_monthly"]
) { eligibilityDict in
let eligible = eligibilityDict["premium_monthly"]?.status == .eligible
}
}
}
What's Included in the Work
- Reading and displaying introductory offer from the
Productobject (StoreKit 2) - Checking eligibility via
Transaction.currentEntitlementsor server-side validation - Paywall UI component with conditional offer display
- Testing via StoreKit Configuration File in Xcode (sandbox without waiting 24 hours)
- Analytics logging: offer display, conversion, offer type
Timeline
3 to 5 days depending on paywall UI complexity and presence of server-side validation. Pricing is calculated individually after requirement analysis.







