Skip to content
All articles

Complete Guide: iOS Subscriptions with RevenueCat & React Native (2026)

Nov 27, 2025· Updated Nov 3, 2026 10 min read
  • Payments
  • RevenueCat
  • iOS
  • Subscriptions
  • Monetization

To sell iOS subscriptions in a React Native or Expo app with RevenueCat, sign Apple's Paid Apps Agreement, create a subscription group and products in App Store Connect, upload an In-App Purchase Key (.p8) to your RevenueCat app, attach the products to an entitlement and an offering, then call Purchases.configure, getOfferings and purchasePackage from react-native-purchases in a development build. RevenueCat is free until your app tracks $2,500 in monthly revenue, then charges 1% of tracked revenue.

This guide follows that order and points out where setups usually break: missing agreements, products stuck in Missing Metadata, and the In-App Purchase Key that SDK 8 and later require.

How the pieces fit together

  • App Store Connect holds your products and their prices, grouped into subscription groups. Apple processes the payment.
  • RevenueCat validates transactions with Apple, tracks who is subscribed, and maps products to entitlements (what the user gets) and offerings (what your paywall shows).
  • Your app uses the SDK to fetch the current offering, start a purchase, and check whether an entitlement is active.

You can change prices and which products the paywall shows from the dashboards without shipping a new build, because the app asks RevenueCat for the current offering at runtime.

What you need first

  • Apple Developer Program membership ($99 a year).
  • The Account Holder's login, or someone with that role, to sign the Paid Apps Agreement.
  • A RevenueCat account. See RevenueCat pricing for the current terms.
  • An Expo development build or a bare React Native app. RevenueCat's Expo guide uses a development build, because the SDK includes native code.
  • A privacy policy and Terms of Use (EULA) URL. Apple rejects subscription apps under Guideline 3.1.2 when these links are missing from the app or its metadata. Apple's standard EULA is acceptable if you don't have your own.

Step 1: Set up App Store Connect

1.1 Sign the Paid Apps Agreement and add banking

In-app purchases don't work, even in the sandbox, until the Paid Apps Agreement is active.

  1. Sign in to App Store Connect as the Account Holder.
  2. Open Business at the top of the page. (Older guides call this section Agreements, Tax, and Banking.)
  3. On the Agreements tab, find Paid Apps and click View and Agree to Terms.
  4. Add your bank account and tax forms. Start this early; Apple has to process them before the agreement shows as active.

1.2 Create a subscription group

  1. Open your app, and in the sidebar under Monetization, click Subscriptions.
  2. Create a group with a reference name such as "Pro".

A user can hold one subscription per group at a time, and moving between products in the same group counts as an upgrade or downgrade. Apple's advice is a single group for most apps; a second group means a user could be billed twice.

1.3 Create the subscription products

  1. In the group, click + and enter a Reference Name and a Product ID such as pro_monthly. Product IDs are permanent and can't be reused, even after you delete the product.
  2. Pick the duration (1 week, 1, 2, 3 or 6 months, or 1 year).
  3. Set the price. Apple generates prices for other countries from your base country, and you can override them.
  4. Add at least one localization with a display name and description.
  5. Under Review Information, upload a screenshot of your paywall and add review notes.
  6. Repeat for pro_annual, then rank the products in the group from most to least access.

When every required field is filled, the product status changes from Missing Metadata to Ready to Submit. Your first subscription has to be submitted for review together with a new app version, so attach it on the version page before you submit.

For a one-time purchase (a "lifetime" plan), create a Non-Consumable under Monetization → In-App Purchases, with the same metadata steps.

Revenue split, from Apple's subscription docs: you receive 70% of the price, minus taxes, during a subscriber's first year, and 85% after they've accumulated a year of paid service in the same group.

1.4 Generate an In-App Purchase Key

Older guides tell you to paste an App-Specific Shared Secret into RevenueCat. That secret is only for StoreKit 1. RevenueCat's React Native SDK 8.0 and later use StoreKit 2, and RevenueCat says transactions fail to be recorded unless an In-App Purchase Key is uploaded.

  1. Go to Users and Access → Integrations → In-App Purchase.
  2. Click Generate In-App Purchase Key (or + if you already have one) and name it.
  3. Download the .p8 file. Apple lets you download it once, so store it in your password manager.
  4. Copy the Issuer ID shown on the same page. If it doesn't appear, create any App Store Connect API key first; the Issuer ID is shared between key types.

One In-App Purchase Key works for every app in the same App Store Connect account.

1.5 Optional: an App Store Connect API key for product import

RevenueCat can import your products and prices if you give it an App Store Connect API key with at least App Manager access (Users and Access → Integrations → App Store Connect API). It also asks for your vendor number, found under Payments and Financial Reports.

Step 2: Configure RevenueCat

  1. Create a project in the RevenueCat dashboard.
  2. Add an App Store app with your exact bundle ID.
  3. Upload the In-App Purchase Key and Issuer ID under the app's In-app purchase key configuration tab. RevenueCat validates them and shows "Valid credentials" when they work.
  4. Add products. Import them with the App Store Connect API key, or type the product IDs exactly as they appear in App Store Connect.
  5. Create an entitlement, for example pro, and attach every product that should grant it.
  6. Create an offering (the first one becomes the default) with packages such as Monthly and Annual, each pointing at a product.
  7. Copy the public Apple SDK key from the API keys page. It's meant to ship in your app. RevenueCat's secret API keys must never go in client code.

Doing it from your coding agent with the RevenueCat MCP server

RevenueCat runs an official, hosted MCP server at https://mcp.revenuecat.ai/mcp. You connect with OAuth (your RevenueCat login) or a v2 API key, and an agent such as Claude Code, Codex or Cursor can then create and edit apps and products for you, along with entitlements, offerings and paywalls. There is no npm package to install; it's a remote server. In Claude Code:

claude mcp add --transport http revenuecat https://mcp.revenuecat.ai/mcp

RevenueCat also packages the server with skills as its AI Toolkit plugin. See the RevenueCat MCP docs for setup per editor.

Step 3: Install and configure the SDK

npx expo install react-native-purchases react-native-purchases-ui

Then make a new development build. Hot reloading an app that was built before the SDK was added fails with new NativeEventEmitter() requires a non-null argument.

// lib/purchases.ts
import { Platform } from 'react-native';
import Purchases, { LOG_LEVEL } from 'react-native-purchases';

export function configurePurchases(appUserID?: string) {
  if (__DEV__) Purchases.setLogLevel(LOG_LEVEL.DEBUG);

  Purchases.configure({
    apiKey: Platform.OS === 'ios'
      ? process.env.EXPO_PUBLIC_RC_APPLE_KEY!
      : process.env.EXPO_PUBLIC_RC_GOOGLE_KEY!,
    appUserID, // optional: your auth user ID, so purchases follow the account
  });
}

Call configurePurchases() once, as early as possible (for example in your root layout's first effect). If users sign in after launch, call Purchases.logIn(userId) when they do and Purchases.logOut() when they sign out.

Step 4: Read entitlements and make purchases

// hooks/usePro.ts
import { useCallback, useEffect, useState } from 'react';
import Purchases, {
  CustomerInfo,
  PurchasesOffering,
  PurchasesPackage,
} from 'react-native-purchases';

const ENTITLEMENT = 'pro';

export function usePro() {
  const [offering, setOffering] = useState<PurchasesOffering | null>(null);
  const [isPro, setIsPro] = useState(false);
  const [loading, setLoading] = useState(true);

  const apply = (info: CustomerInfo) =>
    setIsPro(info.entitlements.active[ENTITLEMENT] !== undefined);

  useEffect(() => {
    Promise.all([Purchases.getOfferings(), Purchases.getCustomerInfo()])
      .then(([offerings, info]) => {
        setOffering(offerings.current);
        apply(info);
      })
      .catch((e) => console.warn('RevenueCat load failed', e))
      .finally(() => setLoading(false));

    Purchases.addCustomerInfoUpdateListener(apply);
    return () => {
      Purchases.removeCustomerInfoUpdateListener(apply);
    };
  }, []);

  const buy = useCallback(async (pkg: PurchasesPackage) => {
    try {
      const { customerInfo } = await Purchases.purchasePackage(pkg);
      apply(customerInfo);
    } catch (e: any) {
      if (!e.userCancelled) throw e; // a cancel is not an error
    }
  }, []);

  const restore = useCallback(async () => {
    apply(await Purchases.restorePurchases());
  }, []);

  return { offering, isPro, loading, buy, restore };
}

Two API names changed in past major versions and still show up in old tutorials: restoreTransactions() is now restorePurchases(), and setDebugLogsEnabled(true) is now setLogLevel(LOG_LEVEL.DEBUG).

Step 5: Show a paywall

Option A: RevenueCat Paywalls

Design the paywall in the RevenueCat dashboard and present it with react-native-purchases-ui. You can then change copy and layout without an app update.

import RevenueCatUI, { PAYWALL_RESULT } from 'react-native-purchases-ui';

export async function showPaywallIfNeeded() {
  const result = await RevenueCatUI.presentPaywallIfNeeded({
    requiredEntitlementIdentifier: 'pro',
  });
  return result === PAYWALL_RESULT.PURCHASED || result === PAYWALL_RESULT.RESTORED;
}

Option B: Your own paywall

import { Pressable, Text, View } from 'react-native';
import { usePro } from '../hooks/usePro';

export function Paywall({ onClose }: { onClose: () => void }) {
  const { offering, isPro, loading, buy, restore } = usePro();

  if (isPro) return <Text>You're subscribed. Thanks!</Text>;
  if (loading || !offering) return <Text>Loading…</Text>;

  return (
    <View style={{ padding: 24, gap: 12 }}>
      {offering.availablePackages.map((pkg) => (
        <Pressable key={pkg.identifier} onPress={() => buy(pkg)}>
          <Text>{pkg.product.title}: {pkg.product.priceString}</Text>
        </Pressable>
      ))}
      <Pressable onPress={restore}><Text>Restore purchases</Text></Pressable>
      <Pressable onPress={onClose}><Text>Not now</Text></Pressable>
      {/* Add links to your Terms of Use and privacy policy here */}
    </View>
  );
}

Show prices with priceString, which comes from the store in the user's currency. Don't hard-code prices or "save 17%" badges; they go stale the day you change a price.

Step 6: Test in the sandbox

  1. Create a sandbox tester in App Store Connect under Users and Access → Sandbox. It needs an email address you can verify.
  2. On the device (iOS 18 or later), add it under Settings → Developer → Sandbox Apple Account. If that row isn't there yet, make a purchase in your development build and sign in when prompted.
  3. Buy, cancel, and restore. In the sandbox, renewals are sped up and a subscription renews at most 12 times a day. TestFlight builds behave differently: since December 2024 their subscriptions renew once every 24 hours.
  4. Check each purchase in the RevenueCat dashboard's customer view (turn on View sandbox data).

Xcode StoreKit configuration files only apply when you run the app from Xcode. Builds started with xcodebuild or npx expo run:ios ignore them.

Troubleshooting

Offerings come back empty

The debug log says none of the products registered in RevenueCat could be fetched from the App Store. Check, in this order: the Paid Apps Agreement is active, the product IDs match exactly, no product is in Missing Metadata, the bundle ID in RevenueCat matches the build, and the products are attached to the current offering.

Purchases go through but RevenueCat doesn't record them

The In-App Purchase Key is missing or invalid. Re-upload it and look for "Valid credentials".

The purchase succeeds but the entitlement stays inactive

The product isn't attached to the entitlement you check in code, or the identifier is spelled differently.

Debug logging

Purchases.setLogLevel(LOG_LEVEL.DEBUG);
const offerings = await Purchases.getOfferings();
console.log(JSON.stringify(offerings.current, null, 2));
const info = await Purchases.getCustomerInfo();
console.log(info.entitlements.active);

App Review checklist for subscriptions

  • A Restore Purchases button. Guideline 3.1.1 asks for a restore mechanism for restorable purchases, and RevenueCat recommends one in every app.
  • The price, billing period and what the user gets, shown before they tap buy.
  • Working links to your Terms of Use (EULA) and privacy policy, in the app and in the App Store metadata (Guideline 3.1.2).
  • The first subscription attached to the app version you submit, with a review screenshot.
  • If the app has accounts: when someone deletes theirs, tell them billing continues through Apple and point them to their subscription settings. Apple's account deletion guidance asks for this.

After launch

RevenueCat's dashboard reports MRR, churn and trial conversion out of the box, and its webhooks and integrations can forward purchase events to your backend or analytics tools. Free trials, intro offers and annual plans are all set per product in App Store Connect. Test them one change at a time rather than copying someone else's numbers.

Where AppSetUpKit fits

AppSetUpKit doesn't include a paywall, purchase hooks or analytics. Its setup wizard installs react-native-purchases, and its RevenueCat guide walks you or your coding agent through the dashboard steps above: the project, App Store app, keys, products, entitlements and offerings. Creating the RevenueCat project itself is a manual dashboard step. The kit's MCP setup guide connects Claude Code or Cursor to RevenueCat's hosted MCP server so the agent can set up products and offerings from a prompt. See what's in the kit.

More guides