How to Restore Purchases in React Native
Overview
When users reinstall your app, switch devices, or sign in on a new install, they need a way to
restore subscriptions and in-app purchases they already paid for. In
react-native-purchases, RevenueCat's restorePurchases() method re-syncs the
user's store purchase history with RevenueCat and updates the CustomerInfo object with any
active entitlements.
This call is user-initiated: it should be wired to a visible "Restore Purchases" button. Apple's App Store Review Guidelines (3.1.1) require apps that sell non-consumable purchases or subscriptions to provide a visible restore mechanism, so a Restore button is effectively mandatory for iOS submission. Common scenarios where restore is needed:
- User reinstalls the app or sets up a new device
- User is signed in with the same Apple ID or Google account that made the original purchase
- Local entitlement state looks wrong and needs to be re-synced from the store
- App review requires a working Restore Purchases button on the paywall
Purchases.configure(...). See the React Native codelab
and the Configure the SDK guide for setup.
Basic Restore Call
The core API is a single async method. Purchases.restorePurchases() resolves with a
CustomerInfo object that reflects the latest entitlement state after re-syncing with the
store. Inspect the active entitlements to decide whether anything was restored:
import Purchases from 'react-native-purchases';
async function restore() {
const customerInfo = await Purchases.restorePurchases();
const isPro = typeof customerInfo.entitlements.active['premium'] !== 'undefined';
if (isPro) {
// Unlock premium features.
} else {
// No active purchases were found for this account.
}
}
Replace 'premium' with the identifier of the entitlement you configured in the RevenueCat
dashboard. customerInfo.entitlements.active is a map keyed by entitlement identifier, so a
typeof ... !== 'undefined' check tells you whether that entitlement is currently active.
A Reusable Hook
In a React Native app it is convenient to wrap the restore flow in a hook that owns the loading state.
The useRestorePurchases hook below exposes a restore function and a
loading boolean so any screen can drive a button without duplicating logic:
import { useState, useCallback } from 'react';
import Purchases from 'react-native-purchases';
type RestoreResult = 'restored' | 'nothing' | 'error' | 'cancelled';
export function useRestorePurchases(entitlementId: string = 'premium') {
const [loading, setLoading] = useState(false);
const restore = useCallback(async (): Promise<RestoreResult> => {
setLoading(true);
try {
const customerInfo = await Purchases.restorePurchases();
const isPro =
typeof customerInfo.entitlements.active[entitlementId] !== 'undefined';
return isPro ? 'restored' : 'nothing';
} catch (e: any) {
// The user dismissing the system dialog is not a real error.
if (e.userCancelled) {
return 'cancelled';
}
console.warn('Restore failed:', e.message);
return 'error';
} finally {
setLoading(false);
}
}, [entitlementId]);
return { restore, loading };
}
The hook always flips loading back off in the finally block, so the button is
re-enabled whether the restore succeeds, finds nothing, is cancelled, or fails. Returning a typed result
lets the caller decide how to present each outcome (a success toast, a "no purchases found" alert, and
so on).
Error Handling
restorePurchases() rejects when something goes wrong, but a user dismissing the system
authentication dialog is a common, expected case that should not be shown as an error.
The caught error may expose a userCancelled boolean and a message string.
Guard on userCancelled before surfacing anything to the user:
import Purchases from 'react-native-purchases';
import { Alert } from 'react-native';
async function restore() {
try {
const customerInfo = await Purchases.restorePurchases();
const isPro =
typeof customerInfo.entitlements.active['premium'] !== 'undefined';
Alert.alert(
isPro ? 'Purchases restored' : 'No purchases found',
isPro
? 'Your premium access has been restored.'
: 'We could not find any purchases for this account.'
);
} catch (e: any) {
// Only show an error when the user did NOT cancel the flow.
if (!e.userCancelled) {
Alert.alert('Restore failed', e.message ?? 'Please try again later.');
}
}
}
userCancelled first. If the user taps the Restore button and
then dismisses the App Store or Google Play sign-in sheet, the promise rejects with
userCancelled === true. Showing an error alert in that case is confusing, so swallow that
path and only report genuine failures.
A Restore Button Component
Combine the hook with a simple component. The button is disabled and shows a spinner while a restore is in flight, and it presents the right message for each result:
import React from 'react';
import { Pressable, Text, ActivityIndicator, Alert, StyleSheet } from 'react-native';
import { useRestorePurchases } from './useRestorePurchases';
export function RestorePurchasesButton() {
const { restore, loading } = useRestorePurchases('premium');
const onPress = async () => {
const result = await restore();
switch (result) {
case 'restored':
Alert.alert('Purchases restored', 'Your premium access is back.');
break;
case 'nothing':
Alert.alert('No purchases found', 'We could not find any purchases for this account.');
break;
case 'error':
Alert.alert('Restore failed', 'Please check your connection and try again.');
break;
case 'cancelled':
// User dismissed the dialog: do nothing.
break;
}
};
return (
<Pressable
style={styles.button}
onPress={onPress}
disabled={loading}
accessibilityRole="button"
accessibilityLabel="Restore Purchases"
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.label}>Restore Purchases</Text>
)}
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#F2545B',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
},
label: { color: '#fff', fontSize: 16, fontWeight: '600' },
});
Place this button somewhere persistent and discoverable, such as the footer of your paywall and a row in your settings screen, so reviewers and returning users can always find it.
Important Notes
restorePurchases() triggers
a store query and a RevenueCat API call. Calling it automatically on every app launch is unnecessary
(RevenueCat already keeps active subscriptions in sync) and slows down startup. Wire it to the Restore
Purchases button only.
restorePurchases() vs syncPurchases().
restorePurchases() is the user-initiated method tied to your Restore button: it re-syncs the
store purchase history and updates CustomerInfo. syncPurchases() is a
lower-level method for migrations and advanced flows that you call programmatically, not from a restore
button. Most apps only need restorePurchases(). See the
Sync Purchases guide for when syncPurchases() applies.
FAQ
How do I restore purchases in react-native-purchases?
Call await Purchases.restorePurchases() inside a try/catch block. It returns a
CustomerInfo object. Check
typeof customerInfo.entitlements.active['premium'] !== 'undefined' to see whether an active
entitlement was restored, and trigger it only from a visible Restore Purchases button.
When should I call restorePurchases()?
Only when the user explicitly taps a Restore Purchases button, typically on a paywall or settings
screen. Do not call it automatically on every app launch.
How do I handle a cancelled restore?
The caught error may have a userCancelled boolean. In your catch block, check
if (!e.userCancelled) before showing an error so you do not display a message when the user
simply dismissed the system dialog.
What is the difference between restorePurchases and syncPurchases?
restorePurchases() is user-initiated and tied to a visible button. syncPurchases()
is a lower-level method for migrations and advanced flows that you call programmatically. Most apps only
need restorePurchases().
Related Guides
- Restore Purchases on iOS (Swift): the iOS sibling of this guide
- Restore Purchases on Android (Kotlin): the Android sibling of this guide
- Sync Purchases: when to use syncPurchases() instead of restore
- Get CustomerInfo & Refresh the Cache: read entitlements and force a refresh
- CustomerInfo Update Listener (React Native): react to entitlement changes after a restore
- React Native In-App Purchases Tutorial: full end-to-end integration
- RevenueCat Docs: official documentation