RevenueCat subscriptions in Expo: setup and test plan
Connect products, offerings, and entitlements in an Expo app, then check purchase, restore, and expiry behavior with a concrete subscription test plan.

Research method
A documentation-based workflow checked on August 31, 2026. The sample is illustrative, not taken from a shipped NaviStack app. Native builds and sandbox purchases have not been executed for this guide.
Build the smallest complete subscription flow first: show a product, start its purchase, check the resulting entitlement, and restore access. Add paywall experiments after that path is understandable and testable.
This guide uses Expo and RevenueCat's React Native SDK as an illustrative stack. It is based on documentation checked on August 31, 2026, not a shipped NaviStack app or a purchase test we ran. The code shows the integration boundaries; it is not a complete drop-in application.
TL;DR
| Step | What matters |
|---|---|
| Prepare the build | Use a native development build for real store purchases. A working Expo Go screen does not validate the purchase flow. |
| Map products and access | Attach products to the entitlement they unlock, then put the packages in an offering. Your feature gate checks access. |
| Configure keys and identity | Use the public SDK key for the correct app and platform. Decide how app accounts, restoration, and transfers should work. |
| Purchase and restore | Buy the selected package and read the resulting entitlement. Handle cancellation and errors, and refresh access as subscription state changes. |
| Test before release | Cover purchase, cancellation, relaunch, restore, expiry, and network failure. Test Store simulation does not replace store sandbox checks. |
Prepare a native development build
Use an Expo project with working iOS or Android builds. Expo's documentation explains that real in-app purchase libraries require native code and a development build. A working JavaScript screen in Expo Go does not establish that a store purchase works. Expo's in-app purchase guide
From your app directory, install the core purchase SDK and development client:
npx expo install expo-dev-client react-native-purchases
Rebuild the native app after adding the dependency; refreshing the JavaScript bundle is insufficient. RevenueCat's Expo instructions cover development builds and optional purchase UI components. Use the build route already configured for your project, and record the resulting Expo and purchase SDK versions in your test notes. RevenueCat's Expo installation
You will also need a RevenueCat project and a test environment. Testing actual store integration requires the corresponding store configuration and tester access. Keep development changes out of a live product catalog unless you intend to change that catalog.
Map products to one access decision
Start with a hypothetical app that has monthly and annual subscriptions granting the same pro access. Write down the identifiers before configuring the dashboard:
| Concept | Illustrative identifier | Role |
|---|---|---|
| Monthly product | example_pro_monthly | What the customer purchases |
| Annual product | example_pro_annual | Another purchase with the same access |
| Entitlement | pro | What your feature gate checks |
| Offering | default | The collection of packages your purchase screen displays |
In RevenueCat, create or import the products, attach them to the entitlement, and place the packages in an offering. Store product identifiers must match the store configuration. The names above are examples, not objects already created in your account. Product configuration
This separation keeps your export screen from having to recognize every product ID. If you later introduce a different billing interval with the same access, the screen can continue checking pro.
Configure the SDK before purchase screens load
For platform-store testing, select the public SDK key for the correct app and platform. The following startup helper deliberately rejects missing configuration and web execution:
import { Platform } from "react-native";
import Purchases from "react-native-purchases";
export function configureSubscriptions() {
const apiKey = Platform.OS === "ios"
? process.env.EXPO_PUBLIC_RC_IOS_KEY
: Platform.OS === "android"
? process.env.EXPO_PUBLIC_RC_ANDROID_KEY
: undefined;
if (!apiKey) {
throw new Error("A platform RevenueCat SDK key is required");
}
Purchases.configure({ apiKey });
}
Call this once through your app's startup path before screens call the SDK. The example omits your app's startup UI and authentication wiring. These EXPO_PUBLIC_ values are embedded in the client; they must contain public SDK keys, never RevenueCat secret API keys or store credentials. RevenueCat API keys
Decide how purchases attach to your account system before adding login. RevenueCat supports anonymous and identified users; signing into an app account requires deliberate identity handling. Check the configured restore and transfer behavior rather than assuming access follows whichever account happens to be open. Identifying customers
Load the offering and preserve the selected package
Read the current offering and handle a missing configuration as a real state:
const offerings = await Purchases.getOfferings();
const packages = offerings.current?.availablePackages ?? [];
if (packages.length === 0) {
// Show a retry or unavailable state; do not invent a fallback price.
}
Render the package's product information using SDK-provided, localized values such as product.priceString. Keep the package the user actually selected and pass that package to the purchase call. Do not automatically buy the first returned product when the user selected a different billing interval. Displaying products
Put the billing interval and trial terms next to the price. A short purchase screen can still be precise. Avoid hardcoding a currency symbol around a localized price string, which can produce a misleading or duplicated price label.
If you would rather use a remotely managed paywall than maintain this screen yourself, compare the editor and integration options in RevenueCat vs Superwall. Choosing a paywall UI does not replace the purchase and access checks below.
Use customer access after purchase and restore
The following functions assume the SDK is configured and the caller handles loading, cancellation, and errors:
import Purchases, { type PurchasesPackage } from "react-native-purchases";
export async function purchaseSelected(selected: PurchasesPackage) {
const { customerInfo } = await Purchases.purchasePackage(selected);
return customerInfo.entitlements.active["pro"] !== undefined;
}
export async function restoreAccess() {
const customerInfo = await Purchases.restorePurchases();
return customerInfo.entitlements.active["pro"] !== undefined;
}
Disable repeated purchase submissions while one is pending. A cancelled or failed purchase must not set a local premium flag. Handle the SDK's cancellation outcome separately from an error that requires the customer's attention. Making purchases
Provide restoration as a visible user action. Restoration and account transfer depend on your identity configuration; they are not a reason to silently merge two signed-in accounts. Restoring purchases
For subsequent access checks, use CustomerInfo and the relevant entitlement. Implement the documented refresh and update-listener behavior for your app lifecycle rather than persisting the purchase callback as permanent access. Client checks also do not replace authorization for a protected server endpoint. Customer information and subscription status
Separate an early simulation from a store test
RevenueCat's Test Store can simulate purchase outcomes without connecting a mobile store. Its documented React Native minimum is 9.5.4; check the requirement against your installed SDK. It uses a Test Store SDK key, distinct from the platform keys in the helper above. Do not put that key in a release build. Test Store documentation
A simulation helps exercise your UI and entitlement handling. It does not validate Apple or Google product setup. Before release, run the actual platform sandbox flow for every store you support. Follow the store-specific tester and installation instructions rather than assuming any installed build is a sandbox build. Apple testing, Google Play testing
Record a result for each case instead of checking off “purchases work”:
| Case | What to observe |
|---|---|
| New purchase | Selected product, transaction, and expected entitlement agree |
| User cancels the purchase sheet | Existing access is unchanged; the screen remains usable |
| Relaunch | Access reflects the customer's current state |
| Restore | The intended account regains the access it owns under your policy |
| Expiry | Access changes when the entitlement becomes inactive |
| Network failure | Retry is possible without granting unsupported access |
Diagnose the first mismatch
If the offering is empty, compare the running app's SDK key, app identifier, store, product IDs, and offering configuration. If a purchase appears but pro is missing, check the product-to-entitlement mapping and the customer ID you are inspecting. If a newly installed native dependency cannot be found, confirm that you rebuilt the development client.
Keep a small evidence record: build version, platform, test environment, anonymized account reference, selected product, expected result, observed result, and a sanitized screenshot or log. This makes a failed test reproducible and keeps a later article from overstating what was tested.
Once this flow works in your own environment, continue with the subscription launch checklist. That adds the account changes, support paths, and release checks that a first successful purchase does not cover.
Keep reading
More practical guides and comparisons selected for this topic.


