The Superwall SDK provides tools to manage subscription status and entitlements, helping you control access to premium features.
Subscription Status
The subscription status indicates whether a user has an active subscription and what entitlements they have access to.
Status Types
There are three possible subscription statuses:
import { useUser } from "expo-superwall" ;
const { subscriptionStatus } = useUser ();
// UNKNOWN - Initial state before status is determined
{ status : "UNKNOWN" }
// INACTIVE - User does not have an active subscription
{ status : "INACTIVE" }
// ACTIVE - User has an active subscription with entitlements
{
status : "ACTIVE" ,
entitlements : [
{ id: "premium" , type: "SERVICE_LEVEL" },
{ id: "pro_features" , type: "SERVICE_LEVEL" }
]
}
Checking Subscription Status
Use the status to control access to features:
function PremiumFeature () {
const { subscriptionStatus } = useUser ();
if ( subscriptionStatus ?. status === "ACTIVE" ) {
return < PremiumContent /> ;
}
return (
< View >
< Text > This feature requires a premium subscription </ Text >
< Button title = "Subscribe" onPress = { showPaywall } />
</ View >
);
}
Listening to Status Changes
React to subscription status changes in real-time:
import { useSuperwallEvents } from "expo-superwall" ;
function App () {
useSuperwallEvents ({
onSubscriptionStatusChange : ( status ) => {
console . log ( "Subscription status changed:" , status . status );
if ( status . status === "ACTIVE" ) {
console . log ( "User is now subscribed!" );
unlockPremiumFeatures ();
} else if ( status . status === "INACTIVE" ) {
console . log ( "User subscription expired" );
lockPremiumFeatures ();
}
},
});
return < YourApp /> ;
}
Entitlements
Entitlements represent the specific features or content a user can access with their subscription.
Fetching Entitlements
Retrieve the user’s entitlements:
import { useUser } from "expo-superwall" ;
function ProfileScreen () {
const { getEntitlements } = useUser ();
const checkEntitlements = async () => {
const entitlements = await getEntitlements ();
console . log ( "Active entitlements:" , entitlements . active );
console . log ( "Inactive entitlements:" , entitlements . inactive );
// Check if user has specific entitlement
const hasPremium = entitlements . active . some ( e => e . id === "premium" );
if ( hasPremium ) {
enablePremiumFeatures ();
}
};
return < Button title = "Check Entitlements" onPress = { checkEntitlements } /> ;
}
Entitlement Structure
interface Entitlement {
id : string ; // Entitlement identifier (e.g., "premium", "pro")
type : "SERVICE_LEVEL" ; // Currently only SERVICE_LEVEL is supported
}
interface EntitlementsInfo {
active : Entitlement []; // Currently active entitlements
inactive : Entitlement []; // Previously active but now expired entitlements
}
Checking Specific Entitlements
Create helper functions to check for specific entitlements:
function useEntitlement ( entitlementId : string ) {
const { subscriptionStatus } = useUser ();
const hasEntitlement =
subscriptionStatus ?. status === "ACTIVE" &&
subscriptionStatus . entitlements . some ( e => e . id === entitlementId );
return hasEntitlement ;
}
// Usage
function ExportFeature () {
const hasPremium = useEntitlement ( "premium" );
const hasPro = useEntitlement ( "pro" );
if ( ! hasPremium && ! hasPro ) {
return < UpgradePrompt /> ;
}
return < ExportButton /> ;
}
Setting Subscription Status
Manually set the subscription status when managing subscriptions outside of Superwall:
Only use setSubscriptionStatus if you’re managing subscriptions manually. The SDK automatically tracks subscription status when using native purchase flows through Superwall paywalls.
import { useUser } from "expo-superwall" ;
function SubscriptionManager () {
const { setSubscriptionStatus } = useUser ();
// After successful purchase
const handlePurchaseSuccess = async ( productId : string ) => {
await setSubscriptionStatus ({
status: "ACTIVE" ,
entitlements: [
{ id: "premium" , type: "SERVICE_LEVEL" }
],
});
};
// After subscription expires or cancellation
const handleSubscriptionEnd = async () => {
await setSubscriptionStatus ({
status: "INACTIVE" ,
});
};
return null ;
}
Syncing with Entitlements
Sync subscription status from the entitlements API:
import { useUser } from "expo-superwall" ;
function useSubscriptionSync () {
const { getEntitlements , setSubscriptionStatus } = useUser ();
const syncSubscription = async () => {
try {
const entitlements = await getEntitlements ();
if ( entitlements . active . length > 0 ) {
// User has active entitlements
await setSubscriptionStatus ({
status: "ACTIVE" ,
entitlements: entitlements . active ,
});
} else {
// No active entitlements
await setSubscriptionStatus ({
status: "INACTIVE" ,
});
}
} catch ( error ) {
console . error ( "Failed to sync subscription:" , error );
}
};
return { syncSubscription };
}
// Usage
function App () {
const { syncSubscription } = useSubscriptionSync ();
useEffect (() => {
// Sync on app launch
syncSubscription ();
}, []);
return < YourApp /> ;
}
Purchase Flow Integration
Handle purchases made through Superwall paywalls:
import { usePlacement , useUser } from "expo-superwall" ;
function PurchaseFlow () {
const { subscriptionStatus , setSubscriptionStatus } = useUser ();
const { registerPlacement } = usePlacement ({
onDismiss : async ( paywallInfo , result ) => {
if ( result . type === "purchased" ) {
console . log ( "Purchase successful:" , result . productId );
// Subscription status is automatically updated by SDK
// But you can manually verify if needed
await verifyPurchase ( result . productId );
} else if ( result . type === "restored" ) {
console . log ( "Purchases restored" );
// Sync subscription status
await syncSubscription ();
}
},
});
const handleSubscribe = async () => {
await registerPlacement ({
placement: "subscription_paywall" ,
feature : () => {
// User already has access or purchased successfully
console . log ( "Access granted!" );
},
});
};
return (
< View >
< Text > Status: { subscriptionStatus ?. status } </ Text >
< Button title = "Subscribe" onPress = { handleSubscribe } />
</ View >
);
}
Complete Example
Here’s a comprehensive subscription management implementation:
import { useUser , usePlacement , useSuperwallEvents } from "expo-superwall" ;
import { View , Text , Button , Alert } from "react-native" ;
import { useEffect , useState } from "react" ;
function SubscriptionScreen () {
const {
subscriptionStatus ,
setSubscriptionStatus ,
getEntitlements ,
} = useUser ();
const [ entitlements , setEntitlements ] = useState < any >( null );
// Listen to subscription status changes
useSuperwallEvents ({
onSubscriptionStatusChange : ( status ) => {
console . log ( "Status changed to:" , status . status );
if ( status . status === "ACTIVE" ) {
Alert . alert ( "Welcome to Premium!" , "You now have access to all features." );
}
},
});
// Configure paywall
const { registerPlacement , state } = usePlacement ({
onPresent : ( info ) => {
console . log ( "Showing paywall:" , info . name );
},
onDismiss : async ( info , result ) => {
if ( result . type === "purchased" ) {
console . log ( "Purchased product:" , result . productId );
// Refresh entitlements
await refreshEntitlements ();
} else if ( result . type === "restored" ) {
console . log ( "Purchases restored" );
await refreshEntitlements ();
}
},
});
// Load entitlements on mount
useEffect (() => {
refreshEntitlements ();
}, []);
const refreshEntitlements = async () => {
try {
const data = await getEntitlements ();
setEntitlements ( data );
// Sync subscription status
if ( data . active . length > 0 ) {
await setSubscriptionStatus ({
status: "ACTIVE" ,
entitlements: data . active ,
});
} else {
await setSubscriptionStatus ({
status: "INACTIVE" ,
});
}
} catch ( error ) {
console . error ( "Failed to fetch entitlements:" , error );
}
};
const handleSubscribe = async () => {
await registerPlacement ({
placement: "subscription" ,
params: {
source: "subscription_screen" ,
},
feature : () => {
console . log ( "User has access!" );
},
});
};
const isActive = subscriptionStatus ?. status === "ACTIVE" ;
return (
< View style = { { flex: 1 , padding: 20 } } >
< Text style = { { fontSize: 24 , marginBottom: 20 } } > Subscription </ Text >
{ /* Status Display */ }
< View style = { { marginBottom: 20 } } >
< Text style = { { fontSize: 18 , marginBottom: 10 } } >
Status: { subscriptionStatus ?. status || "Loading..." }
</ Text >
{ isActive && subscriptionStatus . entitlements && (
< View >
< Text style = { { fontWeight: "bold" , marginTop: 10 } } > Active Entitlements: </ Text >
{ subscriptionStatus . entitlements . map (( e ) => (
< Text key = { e . id } > • { e . id } </ Text >
)) }
</ View >
) }
</ View >
{ /* Entitlements */ }
{ entitlements && (
< View style = { { marginBottom: 20 } } >
< Text style = { { fontWeight: "bold" } } > All Entitlements: </ Text >
< Text > Active: { entitlements . active . map (( e : any ) => e . id ). join ( ", " ) || "None" } </ Text >
< Text > Inactive: { entitlements . inactive . map (( e : any ) => e . id ). join ( ", " ) || "None" } </ Text >
</ View >
) }
{ /* Actions */ }
< View style = { { gap: 10 } } >
{ ! isActive ? (
< Button
title = "Subscribe Now"
onPress = { handleSubscribe }
disabled = { state . status === "presented" }
/>
) : (
< Text style = { { color: "green" , fontSize: 16 } } > ✓ You have an active subscription </ Text >
) }
< Button
title = "Refresh Status"
onPress = { refreshEntitlements }
/>
</ View >
</ View >
);
}
export default SubscriptionScreen ;
Entitlement-Based Feature Gating
Implement feature gating based on entitlements:
import { useUser } from "expo-superwall" ;
import { createContext , useContext } from "react" ;
// Create entitlements context
const EntitlementsContext = createContext < string []>([]);
export function EntitlementsProvider ({ children } : { children : React . ReactNode }) {
const { subscriptionStatus } = useUser ();
const entitlementIds =
subscriptionStatus ?. status === "ACTIVE"
? subscriptionStatus . entitlements . map ( e => e . id )
: [];
return (
< EntitlementsContext.Provider value = { entitlementIds } >
{ children }
</ EntitlementsContext.Provider >
);
}
// Hook to check entitlements
export function useHasEntitlement ( entitlementId : string ) {
const entitlements = useContext ( EntitlementsContext );
return entitlements . includes ( entitlementId );
}
// Feature component with gating
function PremiumExport () {
const hasPremium = useHasEntitlement ( "premium" );
const { registerPlacement } = usePlacement ();
const handleExport = async () => {
if ( ! hasPremium ) {
await registerPlacement ({
placement: "export_paywall" ,
feature : () => performExport (),
});
} else {
performExport ();
}
};
return < Button title = "Export PDF" onPress = { handleExport } /> ;
}
Best Practices
Let Superwall manage status automatically
When using Superwall paywalls for purchases, the SDK automatically updates subscription status. Manual updates are only needed when handling purchases outside of Superwall.
Always sync subscription status when the app launches: useEffect (() => {
syncSubscription ();
}, []);
Cache subscription status locally for offline access: import AsyncStorage from '@react-native-async-storage/async-storage' ;
const cacheSubscriptionStatus = async ( status : SubscriptionStatus ) => {
await AsyncStorage . setItem ( 'subscription_status' , JSON . stringify ( status ));
};
Always verify subscription status on your backend for sensitive operations, never trust client-side status alone.
Next Steps
Displaying Paywalls Learn how to show paywalls and handle purchases
Error Handling Handle errors gracefully in your app