Skip to content

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.

NaviStackSources checked 6 min readPublished
A developer workbench linking an app build, purchase, and entitlement to a test phone
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

StepWhat matters
Prepare the buildUse a native development build for real store purchases. A working Expo Go screen does not validate the purchase flow.
Map products and accessAttach products to the entitlement they unlock, then put the packages in an offering. Your feature gate checks access.
Configure keys and identityUse the public SDK key for the correct app and platform. Decide how app accounts, restoration, and transfers should work.
Purchase and restoreBuy the selected package and read the resulting entitlement. Handle cancellation and errors, and refresh access as subscription state changes.
Test before releaseCover 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:

ConceptIllustrative identifierRole
Monthly productexample_pro_monthlyWhat the customer purchases
Annual productexample_pro_annualAnother purchase with the same access
EntitlementproWhat your feature gate checks
OfferingdefaultThe 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”:

CaseWhat to observe
New purchaseSelected product, transaction, and expected entitlement agree
User cancels the purchase sheetExisting access is unchanged; the screen remains usable
RelaunchAccess reflects the customer's current state
RestoreThe intended account regains the access it owns under your policy
ExpiryAccess changes when the entitlement becomes inactive
Network failureRetry 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.

Found a detail that changed? Send a correction with the source and the section it affects.

More practical guides and comparisons selected for this topic.

All articles