Implementing Consumable In-App Purchases with StoreKit 2

Implementing Consumable In-App Purchases with StoreKit 2

Learn how to start selling Consumable In-App Purchases in your SwiftUI app.

The App Store supports multiple monetization options for your apps. You have a wide variety of options, ranging from charging upfront for your app to offering subscriptions or In-App Purchases for additional functionality.

In this article, we explore how to get started with Consumable In-App Purchases. Consumables are most commonly used for things like coins, credits, or in-game currencies. They allow users to repeatedly purchase a resource that depletes over time.

This flexibility is powerful, but it also comes with responsibility. Consumables are often used in ways that make it hard for users to keep track of how much money they are actually spending. In my opinion, this is where it’s easy to drift into dark pattern territory if you’re not careful. When using consumables, you should be especially mindful about transparency, pricing, and how clearly users understand what they are buying.

In this tutorial, we’ll use a deliberately simple and transparent example. In a photo editor app, users can buy a “Watermark Remover” consumable. Each purchase allows them to export one image without a watermark. Every purchase increments a counter, and consuming the item decrements it again.

The version of Xcode used is 26.2, and iOS 26.

The goal of the tutorial is to guide you through creating, configuring, and testing a Consumable In-App Purchase from Xcode to App Store Connect. By the end, you will have implemented a complete consumable purchase flow using StoreKit 2.

Before We Start

To follow this tutorial successfully, a basic familiarity with Xcode and SwiftUI is helpful, though no prior knowledge of StoreKit is required.

First, create a new project called “PhotoEditor”. Now add a new folder called “Store”. This is where all your purchase-related management files will live. In iOS, Apple provides you with a framework called StoreKit. This framework provides everything you need to sell content in your apps using In-App Purchases. For StoreKit to know about the available In-App Purchases in your app, you need a StoreKit configuration file.

No additional assets are required for this tutorial.

Step 1 – Create the StoreKit configuration file

Choose “New File from Template”, then search for “StoreKit” in the top-right search bar. In our example, we choose the name “PhotoEditor.storekit”. Make sure not to select “Sync this file with an app in App Store Connect.” You don’t need this for local testing, and enabling it only adds unnecessary complexity.

After creating the file, click the “+” button in the bottom-left corner and then choose “Add Consumable In-App Purchase” from the popover menu. Now you have to give your new In-App Purchase a “Reference Name”, which is simply a human-readable name so you can easily identify the product later. You can choose “Watermark Remover,” as this consumable lets the user export one image without a watermark.

You also have to add a “Product ID,” which must be unique across the entire App Store — similar to how your app’s bundle identifier works. A good rule of thumb is to append a component to your app’s bundle identifier, for example, .WatermarkRemover.

Now you have successfully created your first Consumable In-App Purchase. The last step to show this product to a user in your app is to set the “Display Name” and “Description”. I chose “Watermark Remover” as the display name and “Let’s you export one image without a watermark” as the description.

Step 2 – Display the product in the ContentView

Go to the ContentView.swift file and replace the code with the following:

// 1
import StoreKit
import SwiftUI

struct ContentView: View {
    // 2
    @State private var waterMarkRemoverCount: Int = 0

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Available Watermark Removers: \(waterMarkRemoverCount)")
                .font(.headline)

            // 3
            ProductView(id: "dev.xbow.PhotoEditor.WatermarkRemover")
                .productViewStyle(.compact)
                .onInAppPurchaseCompletion { _, result in
                    // 4
                    switch result {
                    case .success(let purchaseResult):
                        // 5
                        handlePurchaseResult(purchaseResult)
                    case .failure:
                        break
                    }
                }

            Button {
                // 9
                if waterMarkRemoverCount > 0 {
                    waterMarkRemoverCount -= 1
                }
            } label: {
                Text("Consume 1 remover")
                    .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .disabled(waterMarkRemoverCount == 0)
        }
        .padding()
    }

    // MARK: - Purchase Handling

    private func handlePurchaseResult(_ result: Product.PurchaseResult) {
        // 6
        switch result {
        case .success(let verificationResult):
            // 7
            if case .verified(let transaction) = verificationResult {
                // 8
                waterMarkRemoverCount += 1
                Task {
                    await transaction.finish()
                }
            }
        default:
            break
        }
    }
}
  1. import StoreKit is required to load StoreKit views and handle purchase results.
  2. The waterMarkRemoverCount state keeps track of how many consumables the user currently owns.
  3. ProductView(id: ...) displays the consumable product using its Product ID.
  4. onInAppPurchaseCompletion is called whenever a purchase attempt finishes.
  5. On success, the purchase result is forwarded to a dedicated handler function.
  6. Product.PurchaseResult represents the outcome of a purchase attempt.
  7. Only verified transactions should be trusted and processed.
  8. When a verified transaction is received, the consumable count is incremented and the transaction is finished so it won’t be delivered again.
  9. The “Consume” button simulates using one consumable by decrementing the counter.

Step 3 – Connect the StoreKit configuration file

When the preview canvas on the right finishes re-rendering, you will see that your consumable In-App Purchase is not shown, even though you correctly passed its Product ID to the ProductView. This happens because your app doesn’t yet know that you have a local StoreKit configuration file that includes the consumable product with this Product ID. Instead, it tries to load the product from App Store Connect, where it doesn’t exist yet, so a placeholder view is shown.

To fix that, you tap on PhotoEditor in the top bar and then choose “Edit Scheme”. Under Options, you will find a dropdown menu next to “StoreKit Configuration” that lets you select the configuration file you just created. If you close the view and switch back to the Preview Canvas, your consumable In-App Purchase should now automatically be displayed with its display name, description, and price.

Step 4 – Test the In-App Purchase on device

Now you can run the app on your device to test the consumable In-App Purchase. When you tap the buy button, a native payment sheet will appear, informing you that you won’t be charged and that this is for testing purposes only.

After tapping “Purchase”, another dialog confirms that the purchase was successful.

You’ll notice that, unlike non-consumables or subscriptions, the purchase button does not become disabled. Instead, the available consumable count increases by one. This reflects the nature of consumables, which can be purchased multiple times.

If you want to inspect what happened under the hood, Xcode includes a handy tool to view all StoreKit transactions in your app. It’s tucked away in the “Debug” menu. You need to select “StoreKit” and then tap on “Manage Transactions”.

This opens a new window where you can see your device and the “PhotoEditor” app on the right-hand side. When you tap it, all transactions will appear in the center section. If you select a consumable transaction, you can view its details on the left-hand side.

Unlike non-consumables or subscriptions, consumable transactions cannot be refunded in the transaction manager because once a consumable is finished, it is considered delivered, and StoreKit does not provide a way to revoke or refund it. Even so, this view remains helpful in verifying that purchases were completed and delivered correctly during testing.

Step 5 – Create the product in App Store Connect

Once you’re done testing locally, it’s time to create the real consumable product in App Store Connect.

Open App Store Connect, navigate to your app, and go to the “In-App Purchases” section.

Here, you create the same In-App Purchase as in the earlier local StoreKit configuration file.

Select “Consumable” as the type and fill in the Reference Name and Product ID with the same values you used locally.

After that, you’ll land on the details section of your newly created In-App Purchase. Here you now have to configure three things:

  1. Availability
  2. Price Schedule
  3. App Store Localization

In the “Availability” section, choose the countries where your consumables should be available. You can keep the preselection for all available countries here.

The following section, “Price Schedule”, is responsible for the price of your consumables in all selected countries. The base price is always set in USD in the first step. For this example, select $0.99.

In the second step, you can review or adjust the automatically generated regional prices. If you don’t need country-specific pricing, you can keep the suggested values.

Step three of the price schedule flow shows an overview of all selected prices. Tap “Confirm” to create the price schedule.

In the final step of creating your consumable In-App Purchase, you configure the App Store localization displayed to users when they purchase the product. Here, you can again copy and paste the Display Name and Description from your local StoreKit configuration file.

Step 6 – Switch back to App Store Connect Products

Your consumable In-App Purchase is now ready to use within your app. To do so, tap on PhotoEditor in the top bar and then choose “Edit Scheme”. Under “Options”, you will find the dropdown menu next to “StoreKit Configuration”, where you now select “None” again. From this point on, your app will load products directly from App Store Connect rather than from the local StoreKit configuration file.

Now you can rerun the app and test the consumable In-App Purchase loaded from App Store Connect. As with local testing, no charge will be incurred during testing.

Final result

And that’s it, you now know how to set up and test a Consumable In-App Purchase using StoreKit 2. We started with a local StoreKit configuration in Xcode, created a consumable product, and tested the purchase flow safely on the device without charging anything. Then we moved on to App Store Connect, set up the real consumable product there, assigned pricing, added localizations, and switched the app to load products directly from App Store Connect.

The workflow of defining products locally first and then mirroring the setup in App Store Connect is the foundation for every future monetization setup you’ll build. Once you understand how these pieces fit together, working with StoreKit becomes much less mysterious and far more predictable.

That said, consumables deserve special care. They are often used for coins, credits, or similar virtual currencies and can be misused to obscure how much money users are actually spending. This is where discussions around dark patterns usually start, and it’s essential to be very conscious about how and why you use this purchase type.

The example used in this tutorial is intentionally simple and exists purely to demonstrate how consumable purchases work from a technical perspective. In a real app, I would not charge users to remove a watermark on every export. Transparency and fairness should always come before maximizing short-term revenue.

The good news is that none of this requires complicated backend work. StoreKit gives you everything you need to implement consumable purchases correctly, and it’s up to you to use that power responsibly.

Where to go next?

Here are helpful resources to continue learning about StoreKit and monetization:

These resources can help you expand your monetization setup, explore other types of In-App Purchases, and think more critically about the ethical implications of different monetization models.