How to Add In-App Subscriptions to an Expo App with RevenueCat (From Prototype to Paid)
A practical, end-to-end guide to adding in-app subscriptions to an Expo React Native app with RevenueCat, covering paywall prototyping, the Expo Go constraint, store configuration, and which monetization model to ship first.
How to Add In-App Subscriptions to an Expo App with RevenueCat (From Prototype to Paid)
If you've been searching for a clear path to add in-app subscriptions to an Expo React Native app with RevenueCat, most guides start with a blank project, walk through SDK installation, and stop there. That's useful, but it skips over the decisions that trip people up in practice: what does the paywall actually look like before any native code is written? What breaks when moving from Expo Go to a real device? And once everything is wired up, which subscription model should be shipped first? This post covers all three, in order, because that's the order problems actually appear.
Step 0: Prototype the Paywall Before Writing a Line of Native Code
The single most overlooked step in a subscription integration is making the paywall screen tappable before touching StoreKit or Google Play Billing. Founders, designers, and developers all lose time implementing a paywall flow that stakeholders haven't agreed on yet. The fix is to get a tappable version in front of people first.
One practical way to do this is with RNBlocks, which generates a live React Native prototype from a plain-language description, running in the browser and on a real device. Describing something like "a journaling app with a home feed, a locked premium section, and a subscription paywall screen" produces a multi-screen flow with real navigation that can be tapped through and shared via link, before any native build is needed. That shareable prototype is the artifact to use in stakeholder or client reviews. It also surfaces layout questions early: does the paywall appear during onboarding, or behind a feature gate? Is there a free tier visible before the paywall, or does the app gate immediately?
Working out those answers before writing a single native module saves meaningful rework. Once the screen structure is agreed on, the downloadable React Native + Expo code from the prototype becomes a starting scaffold for the real implementation. At that point, the task is swapping in real RevenueCat logic rather than building UI from scratch.
Installing react-native-purchases and Understanding the Expo Go Constraint
react-native-purchases is RevenueCat's SDK that wraps StoreKit, Google Play Billing, and RevenueCat Web Billing to make implementing in-app purchases and subscriptions manageable. Installing it in an Expo managed workflow project is straightforward:
npx expo install expo-dev-client react-native-purchases react-native-purchases-ui
expo-dev-client enables development builds. react-native-purchases handles the core purchase logic, and react-native-purchases-ui adds prebuilt paywall components.
Here is the part that catches nearly everyone: Expo Go will not process real purchases, ever. Expo Go is a sandbox that allows rapid prototyping of apps, but it doesn't support running custom native code, such as the native modules required for in-app purchases. react-native-purchases includes a built-in Preview API Mode specifically for Expo Go. When the app runs inside Expo Go, react-native-purchases automatically detects the environment and replaces native calls with JavaScript-level mock APIs. This allows the app to load and execute all subscription-related logic without errors, even though real purchases will not function in this mode.
This means subscription UIs can still be previewed and integration flows tested without needing to build a custom development client immediately. However, to fully test in-app purchases and access real RevenueCat functionality, a development build is required. This distinction matters a lot for vibe coders and indie developers who are used to testing everything directly in Expo Go and assume subscriptions will work the same way. They won't. The moment Purchases.purchasePackage() is called in Expo Go, it returns a mock result, not a real transaction.
The transition to a development build is triggered by running an EAS Build, using the eas tool to start these builds, where EAS stands for Expo Application Services, the cloud services that Expo hosts for building the native binaries. After running eas build --profile development --platform ios (or android), when the build finishes, instructions for installing it on the device are provided. Once installed, the React Native app loads and the RevenueCat integration becomes testable with real store sandbox purchases.
Configuring Products in App Store Connect and Google Play Console
With the development build running, the next task is creating the actual subscription products in both stores and connecting them to a shared RevenueCat entitlement. This step has moving parts on three separate dashboards, so it pays to go in order.
On iOS: Log into App Store Connect and navigate to My Apps. Select the app, then go to the Features tab and open the In-App Purchases section. Click the + button and choose New Subscription. Create a subscription with a reference name, such as monthly_premium_ios, and use the same value for the Product ID. Assign it to a subscription group, creating one if needed.
On Android: To see configured products in an Android app, the APK needs to be uploaded to Google Play. The APK does not need to be released to production, uploading it to an internal track is sufficient. The important thing is that the version number of the APK being tested matches the one set up in Google Play. A Google Cloud service account also needs to be created, granted access to Play Console, and the credentials JSON added to RevenueCat.
In RevenueCat: Create an entitlement (conventionally called premium) that both platform products attach to. This entitlement is what the app checks to determine if the user has paid. Then create an offering with the relevant packages. Offerings let pricing, products, and paywall configuration be changed without an app update, which is one of RevenueCat's most practical features for iterating on monetization. The key mental model here is that the app code always fetches the current offering and displays whatever packages are in it. Changing what users see on the paywall becomes a RevenueCat dashboard operation, not a release.
Supporting subscriptions involves using react-native-purchases within a service file that initializes RevenueCat, fetches packages, handles purchases, and monitors entitlements. These shared hooks keep native logic clean and consistent, ready to plug into the app UI.
The RevenueCat Test Store: A Shortcut Almost No Guide Mentions
Almost every competing guide on this topic jumps straight to Apple Sandbox and Google Play testing without mentioning an option that is genuinely useful for early-stage apps. RevenueCat has a built-in testing environment that works immediately without platform setup, called Test Store. Setting up Test Store requires no additional configuration, the only requirement is using a Test Store API key. Test purchases made through Test Store behave like real purchases and subscriptions, and allow building app logic around subscriptions without having to configure App Store and Google Play subscriptions first.
RevenueCat's Test Store uses a dedicated Test Store API key and simulates real purchase and subscription behavior, including renewals and cancellations. This even works in Expo Go, which makes it useful for quick iteration.
The Test Store is the right choice when the subscription logic needs to be proven out before investing the time in App Store Connect and Play Console configuration. Switch to the platform-specific API key when ready to test real sandbox flows.
This is especially relevant for solo founders or indie developers who are still validating whether a particular paywall layout converts. Test Store removes the two-platform setup overhead entirely during early-stage development. Once the app logic is solid and the paywall design is proven, switching to production-style sandbox testing is a matter of swapping the API key.
Initializing RevenueCat Correctly in Code
RevenueCat provides a backend and SDKs that wrap StoreKit, Google Play Billing, and RevenueCat Web Billing to make implementing in-app purchases easy. The SDK needs to be initialized early in the app lifecycle, and the API key must be platform-specific:
import Purchases from 'react-native-purchases';
import { Platform } from 'react-native';
const API_KEY = Platform.select({
ios: process.env.EXPO_PUBLIC_REVENUECAT_API_KEY_IOS,
android: process.env.EXPO_PUBLIC_REVENUECAT_API_KEY_ANDROID,
});
export async function initRevenueCat(userId?: string) {
if (!API_KEY) return;
await Purchases.configure({
apiKey: API_KEY,
appUserID: userId ?? null,
});
}
If the API key is empty or undefined, calling Purchases.configure crashes the app. Always guard against missing keys, especially in development environments. Calling initRevenueCat early in the app lifecycle, after authentication, and passing the user ID links RevenueCat's subscription state to the authenticated user.
On Android, if the Activity's launchMode is set to anything other than standard or singleTop, backgrounding the app during a purchase (which Google Play sometimes requires for banking verification) can cause the purchase to get cancelled. Set the launchMode to standard or singleTop in the Android app's AndroidManifest.xml to avoid this.
Web products must be configured separately from iOS and Android products in the RevenueCat dashboard, though entitlements can be shared across platforms. This means a single premium entitlement can be active regardless of which platform the user subscribed on.
Choosing a Monetization Model: Freemium, Hard Paywall, or Trial
Once the integration is technically working, the hardest remaining question is which subscription structure to ship. The data from Adapty's State of In-App Subscriptions 2026, based on $3 billion in subscription revenue across 16,000+ apps, provides a concrete foundation for this decision rather than guesswork.
The most important finding for anyone building a first paywall: 89.4% of trial starts happen on Day 0, making the first-session paywall the highest-leverage surface in the entire monetization strategy. The user opens the app, hits the paywall, and decides in that moment. There is no "I'll think about it and come back." This means paywall placement and first-session experience matters far more than trial length or pricing tier. Getting the paywall in front of motivated users during onboarding is the primary lever.
On the hard paywall versus soft paywall question: hard paywalls generate 21% higher LTV per subscriber than soft paywalls. Hard paywalls filter out uncommitted users and attract higher-intent subscribers. The trade-off is lower volume, as soft paywalls convert nearly 50% better on raw conversion rate. For apps with a small but high-value user base, hard paywalls often win on total revenue. For apps optimizing for user count first, a soft paywall or freemium model makes more sense.
On trial strategy, the blanket advice to always offer a free trial is not supported by the data. Trials lift LTV and retention in Utilities, Health and Fitness, and Education. In Productivity and Lifestyle, direct buyers are worth more at 12 months. For anyone building a productivity or creative tool, in Productivity, direct buyers generate $56.95 in 12-month LTV versus $49.13 for trial users. In Lifestyle, trial users end up 21% less valuable than direct buyers. Check which category the app falls into before committing to a trial-first model.
A practical decision checklist:
- Freemium: Best when the free tier provides genuine standalone value and converts through habitual use. Requires a clear upgrade moment tied to a real capability gap.
- Hard paywall: Use when the core value proposition is unambiguous and the target user is already motivated at install. Expect higher LTV per subscriber but lower total volume.
- 7-day trial: Strong choice for Utilities, Health and Fitness, and Education. Less effective in Productivity and Lifestyle. Since 89.4% of trial starts happen on Day 0, optimize the onboarding flow first before adjusting the trial length.
Wrapping Up
Three takeaways worth keeping in mind as this integration comes together:
The Expo Go wall is real. To fully test in-app purchases and access real RevenueCat functionality, a development build is required. Knowing this before starting saves a significant amount of debugging time. Use the RevenueCat Test Store during early-stage development, then transition to EAS-built development builds for proper sandbox testing.
The entitlement model is the right abstraction. Setting up a single
premiumentitlement in RevenueCat, attaching iOS and Android products to it, and checking that entitlement in app code keeps the business logic clean. Changing pricing or plan structure later becomes a dashboard operation, not a code change.Paywall design decisions have outsized consequences. 90% of trial starts happen on Day 0, meaning the onboarding paywall is effectively the entire monetization strategy for most users. Get a tappable version of the paywall in front of real feedback before any native code is written. Build what's agreed on. Then ship it.
Ready to build?
Describe the app and get a live React Native prototype in minutes.