A step-by-step guide to installing RevenueCat's Purchases SDK via Swift Package Manager, configuring it with your API key, and fetching offerings in a native Swift or SwiftUI app.
This guide assumes you already have a working native iOS project in Xcode and an Apple Developer Program membership, since RevenueCat sits on top of Apple's StoreKit rather than replacing any of it. You will also need a RevenueCat account, which is free to create, and a project set up inside the RevenueCat dashboard with your app added under the Apple App Store platform. Have your app's bundle identifier ready and matching between Xcode and the RevenueCat dashboard, because a mismatch is a common source of later confusion when products fail to load. It is helpful, though not strictly required for this step, to have at least one subscription or in-app purchase product created in App Store Connect, so that offerings return real data once the SDK is wired up. Finally, make sure your deployment target meets the SDK's minimum supported iOS version, which RevenueCat documents and periodically raises with new major releases; targeting a reasonably recent iOS release avoids compatibility surprises during installation. If you are on an older Xcode, check the SDK release notes for the matching version before you begin, since the newest SDK may require a recent Xcode and Swift toolchain.
Open your project in Xcode and go to File, then Add Package Dependencies. In the search field at the top right of the dialog, paste the RevenueCat repository URL, https://github.com/RevenueCat/purchases-ios. Xcode will resolve the package and show its available versions; choose the dependency rule Up to Next Major Version so you receive compatible updates without unexpected breaking changes. Click Add Package, and when prompted to choose products, select the RevenueCat library and add it to your app target. If you plan to use RevenueCat's prebuilt paywall UI you may also see an additional RevenueCatUI library you can add, but the core RevenueCat product is all you need for a code-driven paywall. Xcode fetches and links the package automatically. If your team uses CocoaPods instead, you would add pod 'RevenueCat' to your Podfile and run pod install, but Swift Package Manager is the recommended and simplest path for most native projects today. Once resolution completes, you can confirm the dependency appears under Package Dependencies in the Project navigator, and the module becomes importable in your Swift files with import RevenueCat.
RevenueCat authenticates your app using a public SDK API key that is specific to your project and to the Apple platform. In the RevenueCat dashboard, open your project, then navigate to the API keys section under project settings. You will see keys for each platform; for an iOS app you want the public Apple key, which is safe to embed in your client because it only permits the operations an app needs, such as fetching offerings and recording purchases. Do not confuse this with the secret key, which grants privileged server-side access and must never ship inside your app binary. Copy the public Apple API key. It is good practice to store it outside of source control if your team is security conscious, for example by injecting it through a build configuration or an xcconfig file, though for getting started you can reference it directly. Note that because the public key is embedded in every shipped binary, treat it as identifying rather than secret; RevenueCat's server-side controls, not the key's secrecy, are what protect your account. Keep this key handy for the configuration step that follows.
RevenueCat should be configured exactly once, as early as possible in your app's lifecycle. In a SwiftUI app, the natural place is the App struct's initializer. Import the module with import RevenueCat, then call Purchases.configure(withAPIKey:) with your public Apple key. A minimal SwiftUI setup looks like this inside your App type: init() { Purchases.logLevel = .debug; Purchases.configure(withAPIKey: "your_public_apple_key") }. Setting the log level to debug during development surfaces helpful diagnostics in the Xcode console, such as which offerings loaded and whether the SDK reached the backend; you should lower it before release, for example to .info or .warn. In a UIKit app you would place the same configure call in application(_:didFinishLaunchingWithOptions:). After this single call, the shared instance is available everywhere through Purchases.shared, so you never configure it again; calling configure a second time is unnecessary and can cause confusing behavior. Build and run once to confirm the app launches cleanly and the debug logs show a successful configuration without authentication errors, which is the quickest way to catch a wrong or malformed API key early.
Offerings are RevenueCat's server-driven grouping of the products you want to sell, which lets you change your paywall's packages without shipping an app update. To fetch them, call the async API from a context such as a SwiftUI task modifier or an async function: do { let offerings = try await Purchases.shared.offerings(); if let current = offerings.current { /* display current.availablePackages */ } } catch { print(error) }. Each Offering contains Package objects, and each package wraps a StoreProduct with its localized price and title fetched from the App Store. You typically read offerings.current, which is the offering you mark as current in the dashboard, and iterate its availablePackages to build your paywall UI. Because prices come from StoreKit, always display the localizedPriceString rather than hard-coding amounts, so currency and regional formatting are correct for every storefront. If offerings come back empty, that usually points to product or agreement configuration rather than SDK code, which is a separate troubleshooting topic covered in its own guide. A quick sanity check is to log the count of availablePackages so you can immediately see whether the SDK received anything from StoreKit.
Once configuration and fetching are in place, run the app on a real device or the simulator and watch the Xcode console with debug logging enabled. A healthy integration prints that Purchases configured successfully and, when you call offerings(), lists the offerings and packages it retrieved. If you have already created products in App Store Connect and mapped them to offerings in the RevenueCat dashboard, you should see their identifiers and prices appear in the logs. During early development you can also use a StoreKit configuration file in Xcode to test product loading locally without waiting on App Store Connect propagation, which can take time after you create products. Add a simple debug view that lists each package's product identifier and localizedPriceString so you can visually confirm data is flowing rather than reading logs alone. Reaching this point means the SDK is installed, authenticated, and communicating with both RevenueCat and StoreKit, which is the foundation for building your paywall and purchase flow. If configuration succeeds but offerings stay empty, note that as a configuration issue to resolve before you build UI on top of it.
With offerings loading, wiring up a purchase is straightforward and completes the core loop. When a user taps a package on your paywall, call try await Purchases.shared.purchase(package:) with the selected Package. This presents Apple's native StoreKit payment sheet, and RevenueCat brokers the transaction and validates the receipt on its backend. The call returns a result containing an updated CustomerInfo object, from which you read customerInfo.entitlements to see whether the relevant entitlement is now active. A typical pattern is: let result = try await Purchases.shared.purchase(package: package); if result.customerInfo.entitlements["pro"]?.isActive == true { /* unlock features */ }. The same result also exposes a userCancelled flag and the underlying transaction. Wrap the call in a do/catch, and check whether the outcome indicates the user simply cancelled, which is a normal result rather than a failure to surface loudly with an alarming error dialog. This keeps your purchase handling native and honest: Apple processes the money and presents the UI, while RevenueCat gives you a clean entitlement answer to gate features on, so you never parse a receipt by hand.
After the basic flow works, invest in a few practices that pay off in production. Set an app user identifier if you have your own accounts, by passing an appUserID to Purchases.configure or calling Purchases.shared.logIn, so purchases follow the user across devices rather than being tied to an anonymous ID. Listen for CustomerInfo updates using the SDK's async stream, Purchases.shared.customerInfoStream, or the delegate, so your UI reacts when subscriptions renew, expire, or are restored on another device. Lower the log level from debug before you ship, and move your API key out of hard-coded strings if your team requires it. Always implement Restore Purchases, which App Review requires for apps selling non-consumable products or auto-renewable subscriptions, and surface it somewhere visible. Finally, read RevenueCat's official iOS installation and displaying-products documentation for the current API surface, since method signatures and Swift concurrency support evolve across major SDK versions. With configuration, offerings, purchases, entitlement checks, and a restore control in place, you have a complete, native subscription foundation you can build a polished paywall on top of.
Swift Package Manager is the recommended and simplest option for most native iOS projects, and it is integrated directly into Xcode. CocoaPods remains supported for teams already using it, by adding pod 'RevenueCat' to the Podfile, but new projects should generally prefer Swift Package Manager.
Call it exactly once, as early as possible in the app lifecycle. In SwiftUI that is your App struct's init; in UIKit it is application(_:didFinishLaunchingWithOptions:). After that single call, use Purchases.shared everywhere. Never call configure more than once.
Yes. The public Apple SDK key is designed to be embedded in the client and only permits app-level operations like fetching offerings and recording purchases. The secret key is different and must never ship in your app; it is for server-side use only.
Empty offerings almost always mean a configuration issue rather than an SDK bug: products not yet created or approved in App Store Connect, an unsigned Paid Applications Agreement, product identifiers that do not match, or propagation delay. Use a StoreKit configuration file to test locally while App Store Connect data propagates.
To test against real App Store data, yes, you configure products in App Store Connect and map them in RevenueCat. However, you can test product loading and purchase flows earlier using an Xcode StoreKit configuration file, which does not require App Store Connect propagation.