How to Integrate the Superwall iOS SDK in Swift with Swift Package Manager

A step-by-step guide to adding the Superwall SDK to an iOS app via Swift Package Manager, configuring it with your API key, and verifying the integration.

Prerequisites Before You Start

Before integrating the SDK, make sure the surrounding pieces are in place, because Superwall sits on top of them and cannot substitute for any. You need an Xcode project targeting a reasonably recent iOS version, an Apple Developer Program membership, and in-app purchase products already created in App Store Connect for the subscriptions you plan to sell. You also need a Superwall account so you can obtain a public API key from the dashboard. It helps to have at least one placeholder paywall configured in Superwall so that when you wire up the code there is something to present. On the tooling side, confirm your project builds and runs on a device or simulator before adding any dependency, so that if something breaks you know the SDK caused it. Finally, decide early whether Superwall will handle purchasing directly or whether you will provide a purchase controller that routes transactions through another entitlement system such as RevenueCat. That decision affects how you configure the SDK, so it is worth settling before you write the integration code rather than retrofitting it afterward. It is also worth confirming your Paid Applications agreement is active in App Store Connect, because without it StoreKit will not return products and your paywalls will render with blank prices no matter how correct the code is.

Adding the SDK via Swift Package Manager

Superwall is distributed as a Swift package, so the cleanest way to add it is through Xcode's Swift Package Manager integration. In Xcode, open your project, select the project in the navigator, and go to the Package Dependencies tab, or use File then Add Package Dependencies. In the search field enter the Superwall iOS SDK repository URL, https://github.com/superwall/Superwall-iOS, and Xcode will resolve the available versions. Choose a dependency rule pinned to a major version so you get compatible updates without unexpected breaking changes, then confirm. Xcode will fetch the package and present its products; add the Superwall library product to your app target. Once resolution completes you can import the module in Swift with import SuperwallKit. If you use a Package.swift for an SPM-based project rather than an Xcode project, add the same repository URL to your dependencies array with an appropriate version requirement and list the product under your target's dependencies. After adding it, build the project once to confirm the package resolves and links cleanly before writing any configuration code, which isolates dependency problems from configuration problems. If resolution fails, check your network access to GitHub and that your Xcode version is recent enough for the package's minimum Swift tools version, since an outdated toolchain is a common cause of resolution errors.

Configuring Superwall with Your API Key

The SDK must be configured once, as early as possible in the app lifecycle, using the public API key from your Superwall dashboard. In a UIKit app, do this in application(_:didFinishLaunchingWithOptions:) inside your AppDelegate. In a SwiftUI app, do it in your App type's initializer. The call is Superwall.configure(apiKey: "your_public_api_key"). Use the public key that begins with the publishable prefix, not any secret key, and never commit a secret key into the client binary. Configuring early matters because paywall presentation depends on the SDK having fetched your campaign configuration, and calling register before configuration completes will not behave correctly. Keep the key out of source control where practical by injecting it through a build configuration or an xcconfig value rather than hard-coding it inline, especially if the repository is shared. After configuration, the SDK connects to Superwall, downloads your campaigns and paywalls, and is ready to evaluate placements. A single configure call for the app's lifetime is correct; do not call it repeatedly, as that is unnecessary and can cause confusing behavior. If you maintain separate development and production Superwall projects, make sure your debug and release builds inject the matching key for each, or you will test against campaigns that do not correspond to what ships to users.

Choosing a Purchase Handling Strategy

Superwall can complete purchases itself using StoreKit, which is the simplest path and a good default for teams without an existing entitlement system. If you already use another service for entitlements, or you need cross-platform subscription state, you instead supply a PurchaseController when configuring. A purchase controller is an object conforming to Superwall's purchase controller protocol where you implement the methods that perform a purchase, restore purchases, and report the resulting subscription status back to Superwall. When you provide one, Superwall stops calling StoreKit directly for transactions and instead invokes your controller, letting you route the purchase through, for example, RevenueCat's SDK, then update Superwall's subscription status from the resulting entitlement information. You pass this controller in the configure call alongside the API key. Decide this up front because switching later means reworking your purchase flow. If you are unsure and just want to validate the integration, start with Superwall's built-in purchasing, confirm paywalls present and purchase in the sandbox, and layer in a purchase controller afterward once the basic loop works end to end. The critical detail with a custom controller is that you must report subscription status back after every purchase and restore, because if you do not, Superwall keeps treating the user as free and shows the paywall again even after a successful purchase.

Setting Optional User Identity and Attributes

By default Superwall generates an anonymous identifier for each user, which is enough to present paywalls and run experiments. If your app has accounts, you can associate Superwall's identity with your own user identifier by calling Superwall.shared.identify(userId:) after configuration, typically right after a user logs in. This keeps experiment assignment and subscription status consistent for a known user across devices and reinstalls, which matters for accurate conversion tracking. On logout you call Superwall.shared.reset() to return to an anonymous state so a new user is not mistaken for the previous one. You can also set user attributes with Superwall.shared.setUserAttributes(_:), passing a dictionary of traits such as plan preferences, onboarding stage, or acquisition source. These attributes are useful because campaign audience rules can target on them, letting you show different paywalls to different segments without code changes. Keep attributes free of sensitive personal data, and set them at meaningful lifecycle moments rather than on every launch. Identity and attributes are optional for a first integration, but wiring identify and reset early prevents messy data once real users arrive. If you also use RevenueCat, align the identifiers you pass to both SDKs so a purchase recorded in one maps cleanly to the identity that saw the paywall in the other; mismatched identifiers are a common and subtle source of broken conversion attribution.

Verifying the Integration

After adding the package, configuring the API key, and choosing a purchase strategy, verify the integration before building any real paywall logic. First confirm the app launches without a crash and that the console shows Superwall's initialization logs; you can raise the SDK's log level during development to see configuration and campaign fetch messages. Next, add a temporary register call for a test placement, such as Superwall.shared.register(placement: "test_placement"), tied to a button, and set up a matching campaign in the dashboard that presents a paywall for that placement. Tapping the button should display the paywall you configured. If it does, the SDK is talking to your account, downloading campaigns, and presenting correctly. Test on a real device with a sandbox Apple ID as well as the simulator, since purchasing behavior differs between them and the simulator can be unreliable for StoreKit. Watch for common early problems: an incorrect or secret API key, a placement name that does not match the campaign, or configure being called too late. Once a paywall reliably appears from a placement, the base integration is complete and you can move on to building real paywalls and experiments. It is worth also confirming the sandbox purchase actually completes and runs your feature block, because a paywall that presents but cannot purchase points at a StoreKit or product configuration problem rather than a Superwall one.

Keeping the SDK Healthy Over Time

Integration is not a one-time event; the dependency needs ongoing care. Pin the package to a major version and review release notes before upgrading, because paywall SDKs evolve their APIs and a careless bump can break your purchase flow or presentation logic. When you upgrade, test the full paywall-to-purchase loop in the sandbox again, since regressions here directly affect revenue. Keep your API key management clean by injecting it through build configuration rather than committing it, and make sure debug and release builds point at the correct Superwall environment if you use separate ones. Add lightweight monitoring so you notice if paywalls stop presenting in production, for example by logging register calls and their outcomes to your analytics. Because campaign configuration is remote, a dashboard change can alter behavior without a code deploy, so coordinate paywall changes with whoever owns the app. Finally, document in your project how Superwall is configured, which placements exist, and how purchasing is handled, so the next developer understands the boundary between what Superwall controls and what your app and App Store Connect control. A short README section that lists every placement name and the campaign it maps to pays for itself the first time someone renames a placement in the dashboard and wonders why a paywall stopped appearing.

Frequently Asked Questions

What is the exact call to add Superwall via Swift Package Manager?

In Xcode, open File then Add Package Dependencies, enter the repository URL https://github.com/superwall/Superwall-iOS, choose a version rule pinned to a major version, and add the Superwall library product to your app target. Then import it in Swift with import SuperwallKit.

Where should I call Superwall.configure?

Call it once as early as possible: in application(_:didFinishLaunchingWithOptions:) for UIKit apps, or in your App struct's init for SwiftUI. Use Superwall.configure(apiKey: "...") with your public publishable key, never a secret key, and do not call configure more than once per app lifetime.

Which API key do I use?

Use the public publishable API key from your Superwall dashboard, which is safe to include in the client. Never embed a secret key in the app binary. Prefer injecting the key through an xcconfig or build setting rather than hard-coding it, especially in shared repositories.

Do I need a PurchaseController?

Only if you want to handle purchasing yourself or route it through another entitlement system like RevenueCat. Without one, Superwall completes purchases via StoreKit directly, which is the simplest option for validating your integration first. You can add a purchase controller later once the basic flow works.

How do I associate Superwall with my own user accounts?

Call Superwall.shared.identify(userId:) after login to link Superwall's identity to your user ID, and Superwall.shared.reset() on logout to return to anonymous state. This keeps experiment assignment and subscription status consistent for known users across devices and reinstalls.

How do I confirm the SDK is working?

Raise the log level during development, add a temporary register call for a test placement tied to a button, and configure a matching campaign in the dashboard. Tapping the button should present the paywall. Test on both a simulator and a real device with a sandbox Apple ID.