A StoreKit configuration file is a local file in your Xcode project that simulates the App Store, so you can test subscriptions and paywalls right in the simulator with no App Store Connect setup, no sandbox account, and no network. You define your products in the file, point your scheme at it, and StoreKit behaves as if those products were live: purchases succeed, entitlements update, and your paywall renders real prices. It turns subscription testing from a slow round trip through Apple's servers into an instant local loop.
This guide covers what the file is, why local testing beats the sandbox for day-to-day work, how to create and configure the file, how to point your scheme at it, and how to exercise the flows that are painful to reproduce for real: renewals, restores, refunds, Ask to Buy, and billing failures.
On this page
- What a StoreKit configuration file is
- Local file vs the sandbox
- Step 1: Add a configuration file
- Step 2: Define a subscription
- Step 3: Point your scheme at it
- Step 4: Read and buy products in code
- Step 5: Test the hard flows
- Moving from the local file to real products
- FAQ
What a StoreKit configuration file is
It is a .storekit file you add to your Xcode project. Inside it you describe subscription groups, subscriptions, one-time purchases, their prices, and their localizations. When a scheme is set to use it, StoreKit reads products from that file instead of talking to the App Store, and every StoreKit 2 call you make (Product.products(for:), product.purchase(), Transaction.currentEntitlements) resolves against it.
Nothing in your app code changes. You write the same StoreKit 2 you would ship, and the configuration file quietly stands in for Apple's servers while you develop. That is the whole idea: test the real purchase code path without the real purchase infrastructure.
Local file vs the sandbox
Apple gives you two ways to test purchases before production: the StoreKit configuration file (local, in the simulator) and the sandbox environment (Apple's servers, sandbox Apple ID, real devices). Both matter, but they are for different stages.
The sandbox is the more realistic of the two. It exercises Apple's actual servers, receipts, and account flows, which is exactly what you want in a final pass before shipping. It is also slow and awkward for iteration: you need a dedicated sandbox Apple ID, renewals run on compressed but still real clocks, and you cannot test offline.
The local configuration file is the opposite. It is instant, works in the simulator with no account, runs offline, and lets you reset state or fast-forward a year of renewals in seconds. That makes it the right tool for building and debugging your paywall and entitlement logic day to day. The usual workflow is to develop against the local file, then do a sandbox pass before release.
Step 1: Add a configuration file
In Xcode, choose File, New, File, and pick StoreKit Configuration File. Xcode asks one useful question: whether to sync it with an app in App Store Connect.
- Standalone (not synced): you define products by hand in the file. Best when you are starting out or do not have products in App Store Connect yet.
- Synced: Xcode pulls the products you have already created in App Store Connect into the file, so local testing matches your real catalog. Best once your products exist.
Either way you get a .storekit file you edit through Xcode's built-in editor. Start standalone if you are prototyping; switch to synced once the real products are set up so the two never drift.
Step 2: Define a subscription
Open the .storekit file and add a subscription group, then a subscription inside it. In the editor you set the fields that matter for testing:
- Reference Name, internal only.
- Product ID, which must exactly match the identifier you use in code and, later, in App Store Connect. This is the string your app looks products up by, so get it right now.
- Subscription Duration, from one week to one year.
- Price, chosen from the standard price points.
- Localizations, the display name and description your paywall can show.
Add a second subscription to the same group for an annual tier, and you have the monthly-versus-annual choice most apps ship, all testable locally. The one field to be careful with is the product ID: if it does not match what your code requests, Product.products(for:) returns nothing and the paywall looks empty.
Step 3: Point your scheme at it
Adding the file does nothing until a scheme uses it. Open Product, Scheme, Edit Scheme, select Run in the sidebar, open the Options tab, and set StoreKit Configuration to your .storekit file.
From now on, running that scheme routes all StoreKit calls through the file. Switch it back to None to talk to the sandbox or App Store again. It is worth keeping a mental note of which mode a scheme is in, because an empty paywall is often just a scheme still pointed at the sandbox with no signed-in tester.
Step 4: Read and buy products in code
The code is plain StoreKit 2 and is identical whether you are on the local file, the sandbox, or production. Load products by ID:
import StoreKit
let ids = ["com.example.app.pro.monthly", "com.example.app.pro.annual"]
func loadProducts() async throws -> [Product] {
try await Product.products(for: ids)
}Make a purchase and check the result:
func buy(_ product: Product) async throws -> Bool {
let result = try await product.purchase()
switch result {
case .success(let verification):
if case .verified(let transaction) = verification {
await transaction.finish()
return true
}
return false
case .userCancelled, .pending:
return false
@unknown default:
return false
}
}And read current entitlements to decide what to unlock:
func hasActiveSubscription() async -> Bool {
for await result in Transaction.currentEntitlements {
if case .verified(let transaction) = result,
transaction.productType == .autoRenewable,
transaction.revocationDate == nil {
return true
}
}
return false
}Against the configuration file these run instantly and offline, so you can iterate on paywall and unlock logic without leaving the simulator.
Step 5: Test the hard flows
The real payoff is reproducing the states that are slow or impossible to trigger on demand in the sandbox. While your app runs against the configuration file, Xcode's Transaction Manager and the StoreKit testing options let you drive them.
- Renewals, fast. You can accelerate the renewal rate so a monthly subscription renews every few seconds. A year of billing cycles takes under a minute, which is the only sane way to test renewal handling and expiry logic.
- Refunds and revocation. Issue a refund for a test transaction and confirm your app revokes access when the transaction's
revocationDateis set. - Ask to Buy. Simulate a purchase that needs parental approval, so you can test the
.pendingresult and how your UI waits for approval. - Billing failures and grace period. Force a renewal to fail and check that your app handles the billing-retry and grace-period states instead of hard-cutting the user off.
- Reset. Clear all test transactions and start from a clean slate, so every test run begins from a known state.
These are the cases that cause production bugs precisely because they are hard to see during development. The configuration file makes them a menu option instead of a waiting game.
Moving from the local file to real products
Local testing proves your code, not Apple's plumbing. Before release, do a pass in the sandbox to confirm the real servers, receipts, and account flows behave, then rely on production for the final truth.
The one rule that keeps the transition painless is to keep product IDs identical across all three: the .storekit file, App Store Connect, and your code. If they match, moving from local to sandbox to production is just changing the scheme's StoreKit Configuration back to None. If you use RevenueCat, the same product IDs also need to match the products you configure there. For the client side of that, see adding subscriptions to a SwiftUI app with RevenueCat, and for creating the real products through Apple's API rather than by hand, see creating App Store subscriptions with the App Store Connect API.
FAQ
Do I need a StoreKit configuration file if I use RevenueCat?
It still helps. RevenueCat sits on top of StoreKit, and a local configuration file lets you test the purchase and entitlement flow in the simulator without hitting Apple's sandbox. Keep the product IDs in the file matching the ones configured in RevenueCat and App Store Connect.
Can I test subscriptions in the iOS simulator?
Yes, that is exactly what the StoreKit configuration file is for. With a scheme pointed at the file, purchases, renewals, and restores all work in the simulator with no sandbox account and no network.
What is the difference between a StoreKit configuration file and the sandbox?
The configuration file is local, instant, and offline, ideal for day-to-day development. The sandbox uses Apple's real servers and a sandbox Apple ID, which is slower but more realistic. Develop against the file, then do a sandbox pass before release.
Why is my paywall empty when using a StoreKit configuration file?
Almost always a product ID mismatch or a scheme still set to None. Confirm the IDs in the .storekit file exactly match the ones your code requests, and that the scheme's Run options have the StoreKit Configuration set to your file.
How do I test subscription renewals without waiting a month?
Xcode's StoreKit testing options let you accelerate the renewal rate so a monthly subscription renews every few seconds. That lets you test renewal, expiration, and grace-period handling in under a minute.
Does testing with a configuration file cost money?
No. Transactions against a local StoreKit configuration file are simulated and never charge anyone. It is purely a local test harness, separate from real purchases and from the sandbox.
Spaceport generates a .storekit configuration file from the subscriptions you configure and wires it into the app's scheme, so the paywall and purchase flow work in the simulator on the first build, with no App Store Connect metadata required to start. The same product IDs are created in App Store Connect and matched in RevenueCat through their APIs, so the local file, the sandbox, and production all line up. From an indie iOS dev, for indie iOS devs.