integrating-revenuecat
RevenueCat SDK setup, offerings, entitlements, and purchase flows for React Native Expo. Use when implementing in-app purchases, subscriptions, or paywalls.
What this skill does
# RevenueCat Integration Reference
## Plugin Tools
Extract product IDs and entitlements from your codebase:
```bash
# Scan codebase for RevenueCat identifiers
cd /path/to/expo-toolkit && npm run extract-product-ids
# Or specify directory
node tools/extract-product-ids.js /path/to/your/app
node tools/extract-product-ids.js /path/to/your/app --json
```
This extracts:
- Product IDs (e.g., `com.yourapp.premium.monthly`)
- Entitlement names (e.g., `premium`)
- Offering identifiers
- Package types (`$rc_monthly`, `$rc_annual`, etc.)
Use the output to cross-reference with RevenueCat Dashboard.
## RevenueCat Concepts
### Hierarchy
```
RevenueCat Account
└── Project (your app)
└── Apps (iOS, Android, etc.)
└── Products (from App Store Connect / Play Console)
└── Offerings (groups of products)
└── Packages (individual purchasable items)
└── Entitlements (what the user gets)
```
### Key Terms
| Term | Description |
|------|-------------|
| **Product** | An item in App Store Connect or Play Console |
| **Entitlement** | A feature/access level users can unlock |
| **Offering** | A group of packages to present to users |
| **Package** | A specific purchasable item with a product |
| **Customer** | A user identified by app user ID |
## SDK Installation
### For Expo (Managed Workflow)
```bash
npx expo install react-native-purchases
```
### For Expo (Prebuild/Bare)
```bash
npm install react-native-purchases
npx expo prebuild
```
### Plugin Configuration
Add to `app.config.js` or `app.json`:
```javascript
{
"expo": {
"plugins": [
[
"react-native-purchases",
{
"REVENUECAT_API_KEY": "appl_xxxxxxxx", // iOS key
// Or for Android:
// "REVENUECAT_API_KEY": "goog_xxxxxxxx"
}
]
]
}
}
```
**Multi-platform setup:**
```javascript
plugins: [
[
"react-native-purchases",
{
"REVENUECAT_API_KEY_IOS": "appl_xxxxxxxx",
"REVENUECAT_API_KEY_ANDROID": "goog_xxxxxxxx"
}
]
]
```
## SDK Initialisation
### Basic Setup
```typescript
import Purchases, { LOG_LEVEL } from 'react-native-purchases';
import { Platform } from 'react-native';
const API_KEY = Platform.select({
ios: 'appl_xxxxxxxx',
android: 'goog_xxxxxxxx',
});
export async function initPurchases() {
if (__DEV__) {
Purchases.setLogLevel(LOG_LEVEL.VERBOSE);
}
await Purchases.configure({ apiKey: API_KEY });
}
```
### With User Identification
```typescript
export async function initPurchases(userId?: string) {
await Purchases.configure({ apiKey: API_KEY });
if (userId) {
await Purchases.logIn(userId);
}
}
```
### App Initialisation
```typescript
// In App.tsx or root component
useEffect(() => {
initPurchases();
}, []);
```
## Entitlements
### Setting Up Entitlements
In RevenueCat Dashboard:
1. Go to Project Settings → Entitlements
2. Create entitlement (e.g., "premium", "pro")
3. This represents what the user gets access to
### Checking Entitlements
```typescript
import Purchases from 'react-native-purchases';
export async function checkPremiumAccess(): Promise<boolean> {
try {
const customerInfo = await Purchases.getCustomerInfo();
return customerInfo.entitlements.active['premium'] !== undefined;
} catch (error) {
console.error('Error checking entitlements:', error);
return false;
}
}
```
### Using a Hook
```typescript
import { useEffect, useState } from 'react';
import Purchases, { CustomerInfo } from 'react-native-purchases';
export function usePremiumStatus() {
const [isPremium, setIsPremium] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
const checkStatus = async () => {
try {
const customerInfo = await Purchases.getCustomerInfo();
setIsPremium(customerInfo.entitlements.active['premium'] !== undefined);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
checkStatus();
// Listen for changes
const listener = Purchases.addCustomerInfoUpdateListener((info: CustomerInfo) => {
setIsPremium(info.entitlements.active['premium'] !== undefined);
});
return () => listener.remove();
}, []);
return { isPremium, loading };
}
```
## Offerings and Products
### Fetching Offerings
```typescript
import Purchases, { PurchasesOffering } from 'react-native-purchases';
export async function getOfferings(): Promise<PurchasesOffering | null> {
try {
const offerings = await Purchases.getOfferings();
return offerings.current;
} catch (error) {
console.error('Error fetching offerings:', error);
return null;
}
}
```
### Displaying Products
```typescript
const offering = await getOfferings();
if (offering) {
offering.availablePackages.forEach(pkg => {
console.log('Package:', pkg.identifier);
console.log('Product:', pkg.product.title);
console.log('Price:', pkg.product.priceString);
console.log('Description:', pkg.product.description);
});
}
```
### Package Types
| Type | Description |
|------|-------------|
| `$rc_monthly` | Monthly subscription |
| `$rc_annual` | Annual subscription |
| `$rc_weekly` | Weekly subscription |
| `$rc_lifetime` | Lifetime (one-time) purchase |
| Custom | Your own identifier |
## Making Purchases
### Purchase Flow
```typescript
import Purchases, { PurchasesPackage } from 'react-native-purchases';
export async function purchasePackage(pkg: PurchasesPackage): Promise<boolean> {
try {
const { customerInfo } = await Purchases.purchasePackage(pkg);
if (customerInfo.entitlements.active['premium']) {
return true;
}
return false;
} catch (error: any) {
if (error.userCancelled) {
// User cancelled, not an error
return false;
}
throw error;
}
}
```
### Complete Paywall Component
```typescript
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native';
import Purchases, { PurchasesOffering, PurchasesPackage } from 'react-native-purchases';
export function Paywall({ onPurchase }: { onPurchase: () => void }) {
const [offering, setOffering] = useState<PurchasesOffering | null>(null);
const [loading, setLoading] = useState(true);
const [purchasing, setPurchasing] = useState(false);
useEffect(() => {
const fetchOfferings = async () => {
try {
const offerings = await Purchases.getOfferings();
setOffering(offerings.current);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
fetchOfferings();
}, []);
const handlePurchase = async (pkg: PurchasesPackage) => {
setPurchasing(true);
try {
const { customerInfo } = await Purchases.purchasePackage(pkg);
if (customerInfo.entitlements.active['premium']) {
onPurchase();
}
} catch (error: any) {
if (!error.userCancelled) {
console.error('Purchase error:', error);
}
} finally {
setPurchasing(false);
}
};
const handleRestore = async () => {
setPurchasing(true);
try {
const customerInfo = await Purchases.restorePurchases();
if (customerInfo.entitlements.active['premium']) {
onPurchase();
}
} catch (error) {
console.error('Restore error:', error);
} finally {
setPurchasing(false);
}
};
if (loading) {
return <ActivityIndicator size="large" />;
}
return (
<View style={styles.container}>
<Text style={styles.title}>Upgrade to Premium</Text>
{offering?.availablePackages.map(pkg => (
<TouchableOpacity
key={pkg.identifier}
style={styles.package}
onPress={() => handlePurchase(pkg)}
disabled={purchasing}
>
<Text style={styles.packageTitle}>{pkg.product.title}</Text>
<Text style=Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.