Welcome to RevenueCat's React Native SDK Codelab!
Goal: In this codelab, you will learn how to integrate the RevenueCat React Native SDK into a new React Native application to fetch product offerings and display a pre-built, native paywall UI.
What you'll build:
A simple one-screen app that shows a loading indicator, fetches your configured "Offering" from RevenueCat, and then displays the RevenueCatUI paywall.
Before you start, you must have the following set up:
By the end of this codelab, you'll be able to successfully implement in-app purchases in your React Native app and display dynamic paywalls using RevenueCat's React Native SDK.

First things first, before implementing in-app purchases, you'll need to import the RevenueCat SDK into your existing or new project. To get started, add the following dependency to your project:
You can check out the latest release version on GitHub.
Using npm:
npm install --save react-native-purchases
Or using yarn:
yarn add react-native-purchases
For iOS, you need to enable the "In-App Purchase" capability:
For Android, add the billing permission to your AndroidManifest.xml:
<uses-permission android:name="com.android.vending.BILLING" />
Also, ensure your Activity's launchMode is set to standard or singleTop in AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="standard"
...
/>
Now, let's initialize the RevenueCat SDK. We will do this in your main App.js or App.tsx file.
App.js (or App.tsx for TypeScript projects)// App.js
import React, { useEffect, useState } from 'react';
import { Platform, View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import Purchases from 'react-native-purchases';
// --- PASTE YOUR REVENUECAT KEYS HERE ---
const androidApiKey = 'goog_YOUR_KEY_HERE';
const iosApiKey = 'appl_YOUR_KEY_HERE';
const App = () => {
const [isConfigured, setIsConfigured] = useState(false);
useEffect(() => {
const initializePurchases = async () => {
try {
// Enable debug logs for development
Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG);
// Configure the SDK with the appropriate API key
if (Platform.OS === 'ios') {
await Purchases.configure({ apiKey: iosApiKey });
} else if (Platform.OS === 'android') {
await Purchases.configure({ apiKey: androidApiKey });
}
console.log('RevenueCat configured successfully!');
setIsConfigured(true);
} catch (e) {
console.error('Error configuring RevenueCat:', e);
}
};
initializePurchases();
}, []);
if (!isConfigured) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Initializing RevenueCat...</Text>
</View>
);
}
// Main app content will go here in the next steps
return (
<View style={styles.container}>
<Text>RevenueCat is ready!</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 10,
fontSize: 16,
},
});
export default App;
We just created a basic React Native app that initializes the RevenueCat SDK using the useEffect hook. This ensures the SDK is configured and ready before any purchases are made. This is the correct way to initialize plugins that need to be available right from the start.
Yes! You've now completed 50% of the implementation.
Now let's move on to validating user entitlements.
As mentioned earlier, an entitlement represents the level of access or features a user unlocks after making a purchase. This makes it useful for determining things like whether to display an ad banner or grant premium access.
You can easily check if a user has an active entitlement using the code snippet below:
const ENTITLEMENT_IDENTIFIER = ".."; // get specific entitlement identifier from your RevenueCat dashboard
try {
const customerInfo = await Purchases.getCustomerInfo();
const isEntitled = customerInfo.entitlements.active[ENTITLEMENT_IDENTIFIER]?.isActive;
if (isEntitled) {
// User has access to this entitlement
console.log('User is entitled');
} else {
// User does not have access
console.log('User is not entitled');
}
} catch (e) {
console.error('Error checking entitlement:', e);
}
Once you've checked whether the user has a specific entitlement, you can decide how to proceed based on your app's business model.
For example, if your app is ad-supported, you might choose to show or hide an AdMob banner. Alternatively, you could choose to display a paywall or purchase dialog, allowing users to unlock advanced features or content.
Here's an example of how you might implement that logic:
if (isEntitled) {
// if the user is granted access to this entitlement, don't need to display a banner.
} else {
// display a banner UI here or display a paywall
}
Now, let's implement in-app purchases to offer an ad-free experience. To get started, you'll first need to fetch the relevant product information from your RevenueCat dashboard. This product data will be used to present purchase options to your users.
You can retrieve the available products by calling Purchases.getProducts(), as shown in the example below:
// Fetches the product information from the RevenueCat server
const products = await Purchases.getProducts(['paywall_tester.subs']);
// Proceed with in-app purchase
const purchaseResult = await Purchases.purchaseStoreProduct(products[0]);
If you offer multiple product variations, such as paywall_tester.subs:weekly, paywall_tester.subs:monthly, and paywall_tester.subs:yearly, you can simplify product retrieval by using the base product identifier, paywall_tester.subs, as the value for the productIds field. This tells RevenueCat to fetch all related product variations as a list, so you can dynamically present them in your paywall UI.
Once you've retrieved the product data, you can initiate the in-app purchase flow by calling Purchases.purchaseStoreProduct(product). This will automatically trigger the Google Play purchase dialog or Apple's purchase sheet, enabling the user to complete the transaction within your app.
Just like that, you've integrated a fully functional in-app purchase flow with just a few lines of code—no need to deal with the complexity of handling receipts, store APIs, or purchase validation manually.
So the full example of code will look like below:
import Purchases from 'react-native-purchases';
/**
* Fetches a specific product by its ID and initiates the purchase flow.
*
* This function handles potential errors like the product not being found,
* the user canceling the purchase, or other store errors.
*/
const purchaseProduct = async () => {
// 1. Define the product identifier you want to fetch.
const productId = 'paywall_tester.subs';
try {
// 2. Fetch the StoreProduct(s) from RevenueCat
console.log('Fetching products...');
const products = await Purchases.getProducts([productId]);
// 3. Check if the product list is not empty
if (products.length === 0) {
console.error('Error: Product not found. Check the ID and your RevenueCat setup.');
// Optionally, show an error message to the user
return;
}
const productToPurchase = products[0];
console.log('Product found:', productToPurchase.title, '. Initiating purchase...');
// 4. Initiate the purchase flow
const { customerInfo } = await Purchases.purchaseStoreProduct(productToPurchase);
// 5. Check if the purchase was successful by verifying the entitlement
// Replace "your_premium_entitlement" with your actual entitlement identifier from RevenueCat
if (customerInfo.entitlements.active['your_premium_entitlement']?.isActive) {
console.log('Purchase successful! User now has premium access.');
// Grant access to premium content
} else {
console.log('Purchase completed, but entitlement is not active.');
}
} catch (e) {
// 6. Handle potential errors
if (e.code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
console.log('Purchase cancelled by user.');
} else {
console.error('Purchase failed with error:', e.message);
}
}
};
Now, it's time to implement Paywalls in React Native.
First, install the RevenueCatUI package that provides pre-built paywall components:
npm install --save react-native-purchases-ui
# or
yarn add react-native-purchases-ui
Import the paywall presentation method at the top of your file:
import { presentPaywallIfNeeded } from 'react-native-purchases-ui';
Let's update our App.js to fetch offerings and display the paywall. Replace your existing app code with:
// App.js
import React, { useEffect, useState } from 'react';
import {
Platform,
View,
Text,
ActivityIndicator,
StyleSheet,
SafeAreaView,
} from 'react-native';
import Purchases from 'react-native-purchases';
import { presentPaywallIfNeeded } from 'react-native-purchases-ui';
// --- PASTE YOUR REVENUECAT KEYS HERE ---
const androidApiKey = 'goog_YOUR_KEY_HERE';
const iosApiKey = 'appl_YOUR_KEY_HERE';
const App = () => {
const [isConfigured, setIsConfigured] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [offering, setOffering] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
useEffect(() => {
const initializePurchases = async () => {
try {
// Enable debug logs for development
Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG);
// Configure the SDK with the appropriate API key
if (Platform.OS === 'ios') {
await Purchases.configure({ apiKey: iosApiKey });
} else if (Platform.OS === 'android') {
await Purchases.configure({ apiKey: androidApiKey });
}
console.log('RevenueCat configured successfully!');
setIsConfigured(true);
} catch (e) {
console.error('Error configuring RevenueCat:', e);
setErrorMessage('Failed to initialize RevenueCat');
setIsLoading(false);
}
};
initializePurchases();
}, []);
useEffect(() => {
if (!isConfigured) return;
const loadOfferings = async () => {
try {
setIsLoading(true);
setErrorMessage(null);
// Get the current offerings from RevenueCat
const offerings = await Purchases.getOfferings();
if (offerings.current !== null) {
setOffering(offerings.current);
} else {
setErrorMessage('No current offering found. Check your RevenueCat dashboard.');
}
} catch (e) {
console.error('Error fetching offerings:', e);
setErrorMessage(`Failed to load offerings: ${e.message}`);
} finally {
setIsLoading(false);
}
};
loadOfferings();
}, [isConfigured]);
useEffect(() => {
if (!offering) return;
const showPaywall = async () => {
try {
const paywallResult = await presentPaywallIfNeeded({
offering: offering,
});
console.log('Paywall result:', paywallResult);
if (paywallResult === Purchases.PAYWALL_RESULT.PURCHASED) {
console.log('User made a purchase!');
} else if (paywallResult === Purchases.PAYWALL_RESULT.RESTORED) {
console.log('User restored purchases!');
} else if (paywallResult === Purchases.PAYWALL_RESULT.CANCELLED) {
console.log('User cancelled the paywall');
}
} catch (e) {
console.error('Error presenting paywall:', e);
}
};
showPaywall();
}, [offering]);
if (isLoading) {
return (
<SafeAreaView style={styles.centered}>
<ActivityIndicator size="large" color="#6366f1" />
<Text style={styles.loadingText}>Loading Paywall...</Text>
</SafeAreaView>
);
}
if (errorMessage) {
return (
<SafeAreaView style={styles.centered}>
<Text style={styles.errorText}>{errorMessage}</Text>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>RevenueCat Paywall Demo</Text>
<Text style={styles.subtitle}>Paywall displayed successfully!</Text>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
padding: 20,
},
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: '#666',
},
errorText: {
fontSize: 16,
color: '#ef4444',
textAlign: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 8,
color: '#111',
},
subtitle: {
fontSize: 16,
color: '#666',
},
});
export default App;
What did we just do?
Purchases.getOfferings().presentPaywallIfNeeded().You're Done!
Now, run your application on a device or emulator:
For iOS:
npx react-native run-ios
For Android:
npx react-native run-android
You should see:

If the paywall doesn't appear, check the debug console and review these common issues:
Configuration! 🥳 Now, you'll be able to display paywalls whenever a user doesn't have the required entitlement, using the exact same design you configured in the Paywall Editor.
As you've already seen in the Codelab: RevenueCat Google Play Integration (Create Paywalls), the paywall system is built on a server-driven UI. This means you can dynamically update the paywall's content and design directly from the dashboard without needing to push app updates or go through the review process.
In this codelab, you've learned how to integrate RevenueCat's React Native SDK, implement in-app purchases, and build paywalls in React Native. Now it's time to ship your app and make more money! 💰
You can also learn more about using the RevenueCat SDK with the resources below: