Skip to navigation

Check Entitlements

This guide shows you how to check if a user has an active subscription and gate premium content accordingly. You’ll also learn how to handle user login/logout to sync subscriptions across devices.

What you’ll learn

  • Check if a user has access to a product
  • Gate content based on subscription status
  • Sync subscriptions across devices with user login

Prerequisites

Check subscription status

Use hasAccess(to:) to check if the user has access to a specific product:

struct ContentView: View {
@Environment(Checkout.self) var checkout
var body: some View {
VStack {
// Basic features always available
BasicFeaturesView()
// Premium features for subscribers
if checkout.hasAccess(to: "prod_xxxxxxxxxxxxxx") {
PremiumFeaturesView()
}
// If you have multiple tiers, check each product separately
if checkout.hasAccess(to: "prod_yyyyyyyyyyyyyy") {
EnterpriseFeaturesView()
}
}
}
}

Or use isSubscribed to check if the user has any active subscription:

if checkout.isSubscribed {
// User has at least one active membership
}

View active memberships

Access the memberships array to show subscription details:

struct SubscriptionStatusView: View {
@Environment(Checkout.self) var checkout
var body: some View {
List {
if checkout.memberships.isEmpty {
Text("No active subscriptions")
.foregroundStyle(.secondary)
} else {
ForEach(checkout.memberships) { membership in
MembershipRow(membership: membership)
}
}
}
}
}
struct MembershipRow: View {
let membership: CheckoutMembership
var body: some View {
VStack(alignment: .leading) {
Text("Subscription")
.font(.headline)
if let expiresAt = membership.expiresAt {
Text("Renews \(expiresAt, style: .date)")
.foregroundStyle(.secondary)
}
Text("Status: \(membership.status.rawValue)")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}

User login and logout

User IDs allow subscriptions to sync across devices. When a user logs in, the SDK automatically claims any unclaimed memberships from the current device and loads their existing memberships.

Log in a user

func handleLogin(userId: String) async {
do {
try await checkout.logIn(appUserId: userId)
// Memberships are now synced for this user
} catch {
print("Login failed: \(error)")
}
}

Log out a user

func handleLogout() {
checkout.logOut()
// User is now logged out, memberships cleared
}

Check the current user

if let userId = checkout.appUserId {
Text("Logged in as \(userId)")
} else {
Text("Not logged in")
}

Guest purchases

Users can purchase subscriptions even without logging in. The SDK tracks these purchases by device ID and automatically claims them when the user logs in.

struct PurchaseFlow: View {
@Environment(Checkout.self) var checkout
@State private var showingLogin = false
var body: some View {
VStack {
if checkout.appUserId == nil {
// User not logged in - they can still purchase
Text("Purchase as guest or log in to sync across devices")
Button("Continue as Guest") {
// Purchase will be tied to device
Task { try? await checkout.purchase("plan_xxx") }
}
Button("Log In") {
showingLogin = true
}
} else {
// User logged in - purchase tied to their account
Button("Subscribe") {
Task { try? await checkout.purchase("plan_xxx") }
}
}
}
.sheet(isPresented: $showingLogin) {
// Your login view here
}
}
}

Complete example

Here’s a complete example showing entitlement checking with user auth:

struct MainView: View {
@Environment(Checkout.self) var checkout
@State private var showingPaywall = false
@State private var showingLogin = false
var body: some View {
NavigationStack {
Group {
if !checkout.isInitialized {
ProgressView("Loading...")
} else if checkout.hasAccess(to: "prod_xxxxxxxxxxxxxx") {
PremiumContentView()
} else {
FreeContentView(onUpgrade: { showingPaywall = true })
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
if let userId = checkout.appUserId {
Text("Signed in as \(userId)")
Button("Sign Out", action: signOut)
} else {
Button("Sign In") { showingLogin = true }
}
Divider()
if checkout.hasAccess(to: "prod_xxxxxxxxxxxxxx") {
Button("Manage Subscription") {
// Show subscription management
}
}
} label: {
Image(systemName: "person.circle")
}
}
}
.sheet(isPresented: $showingPaywall) {
PaywallView()
}
.sheet(isPresented: $showingLogin) {
LoginView { userId in
Task {
try? await checkout.logIn(appUserId: userId)
showingLogin = false
}
}
}
}
}
func signOut() {
checkout.logOut()
}
}
struct FreeContentView: View {
let onUpgrade: () -> Void
var body: some View {
VStack(spacing: 20) {
Text("Free Content")
.font(.title)
Text("Upgrade to access premium features")
.foregroundStyle(.secondary)
Button("Upgrade Now", action: onUpgrade)
.buttonStyle(.borderedProminent)
}
}
}
struct PremiumContentView: View {
var body: some View {
VStack {
Image(systemName: "crown.fill")
.font(.largeTitle)
.foregroundStyle(.yellow)
Text("Premium Content")
.font(.title)
Text("Thanks for subscribing!")
}
}
}

Reactive updates

The SDK automatically updates views when subscription status changes. Since Checkout is @Observable, any view using @Environment(Checkout.self) will re-render when:

  • A purchase completes
  • A user logs in or out
  • Memberships are loaded or updated

No manual refresh is needed.

Next steps