Unlocking Purchased Content

Deliver content to the user after validating the purchase.


Once a purchase is completed, it's your responsibility to make sure that you programmatically make the content available to the user.

Identify the Purchased Content

For an in-app purchase product that enables app functionality, such as a premium subscription, set a Boolean value to enable the code path and update your user interface as needed. Consult a persistent record of transactions that occur in your app to determine the functionality to unlock. Your app must update this Boolean value whenever the user completes a purchase and at app launch. For information on making a persistent record, see Persisting a Purchase.

For example, using the app receipt, your code might look like the following:

guard let receiptURL = Bundle.main.appStoreReceiptURL else { customError() }
do {
    let receiptData = try Data(contentsOf: receiptURL)
    // Custom method to work with receipts
    let rocketCarEnabled = receipt(receiptData, includesProductID: "com.example.rocketCar")
    } catch {

Or, using the user defaults system:

let defaults = UserDefaults.standard
let rocketCarEnabled = defaults.bool(forKey: "enable_rocket_car”)

After you define the Boolean variable, use the purchase information to enable the appropriate code paths in your app:

if (rocketCarEnabled) {
    // Use the rocket car.
} else {
    // Use the regular car.

Deliver Associated Content

Your app must deliver any content associated with the purchased product to the user. For example, purchasing instruments in a music app requires delivering the sound files needed to let the user play those instruments. You can embed that content in your app’s bundle or download it as needed. Each approach has its advantages and disadvantages.

Embed smaller files (up to a few megabytes) in your app, especially if you expect most users to buy that product. You can make the content in your app bundle available immediately after the user purchases it. However, to add or update content in your app bundle, you must submit an updated version of your app.

Download larger files only when needed. Separating content from your app bundle keeps your app’s initial download small. For example, a game can include the first level in its app bundle and let users download additional levels as they purchase them. Assuming your app fetches its list of product identifiers from your server and the information isn't hard-coded in the app bundle, you don't need to resubmit your app to add or update content that your app downloads.

Load Local Content

Load local content using the NSBundle class as you load other resources from your app bundle.

guard let url = Bundle.main.url(forResource: "rocketCar", withExtension: "plist") else { fatalError() }
loadVehicle(at: url)

Download Hosted Content from Apple’s Server

Most apps should use Apple-hosted content for downloaded files. You create an Apple-hosted content bundle using the In-App Purchase Content target in Xcode and submit it to App Store Connect. Apple's servers store your app’s content using the same infrastructure that supports other large-scale operations, such as the App Store. Apple-hosted content automatically downloads in the background even if your app is not running.

If you need to support older versions of iOS or share your server infrastructure across multiple platforms, you may choose to host your own content using your own server infrastructure.

When the user purchases a product that has associated Apple-hosted content, the transaction passed to your transaction queue observer also includes an instance of SKDownload that lets you download the associated content.

To download the content, add the download objects from the transaction’s downloads property to the transaction queue by calling the start(_:) method of SKPaymentQueue. If the value of the downloads property is nil, there’s no Apple-hosted content for that transaction. Unlike downloading apps, downloading content does not automatically require a Wi-Fi connection for content larger than a certain size. Avoid using cellular networks to download large files without an explicit action from the user.

Implement the paymentQueue(_:updatedDownloads:) method on the transaction queue observer to respond to changes in a download’s state, such as by updating progress in your UI. If a download fails, use the information in its error property to present the error to the user.

Ensure that your app handles errors gracefully. For example, if the device runs out of disk space during a download, give the user the option to discard the partial download or to resume the download later when space becomes available.

While the content is downloading, update your user interface using the values of the progress and timeRemaining properties. You can use the pause(_:), resume(_:), and cancel(_:) methods of SKPaymentQueue from your UI to let the user control in-progress downloads. Use the downloadState property to determine whether the download has completed. Don’t use the progress or timeRemaining property of the download object to check its status; these properties are for updating your UI.

In iOS, your app can manage the downloaded files. The StoreKit framework saves these files for you in the Caches directory with the backup flag unset. After the download completes, your app is responsible for moving these files to the appropriate location. For content that can be deleted if the device runs out of disk space (and downloaded again later by your app), keep the files in the Caches directory. Otherwise, move the files to the Documents folder and set the flag to exclude them from user backups.

// This is the downloaded content url.
guard var url = download.contentURL else { fatalError() }

var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true

do {
    try url.setResourceValues(resourceValues)
} catch {

In macOS, the system manages the downloaded files; your app can’t move or delete them directly. To locate the content after downloading it, use the contentURL property of the download object. To locate the file on subsequent launches, use the contentURL(forProductID:) class method of SKDownload. To delete a file, use the deleteContent(forProductID:) class method. For information about reading the app receipt, see Validating Receipts with the App Store.

Download Content from Your Own Server

As with all other interactions between your app and your server, the details and mechanics of the process of downloading content from your own server are your responsibility. The communication consists of, at a minimum, the following steps:

  1. Your app sends the receipt to your server and requests the content.

  2. Your server validates the receipt to establish that the content has been purchased, as described in Validating Receipts with the App Store.

  3. Assuming the receipt is valid, your server responds to your app with the content.

Ensure that your app handles errors gracefully. For example, if the device runs out of disk space during a download, give the user the option to discard the partial download or to resume the download later when space becomes available.

Consider the security implications of how you host your content and how your app communicates with your server. For more information, see Security Overview.

See Also

Content Delivery

Persisting a Purchase

Keep a persistent record of a purchase to continue making the product available as needed.

Finishing a Transaction

Finish the transaction to complete the purchase process.

class SKDownload

Downloadable content associated with a product.