RORK LABJP
CLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/monthCLOUD — Rork Max compiles native Swift on a fleet of cloud Macs, so you never download Xcode or need to own a MacPLATFORM — Rork Max targets iPhone, iPad, Apple Watch, and Vision Pro, and reaches games, widgets, and Live ActivitiesSHIP — Build in the browser, preview through a streaming simulator, install on device via QR code, and submit to the App Store without leaving RorkSPLIT — Regular Rork generates cross-platform apps with React Native and Expo. Reach for it to ship broadly and fast, and for Max when you need Apple-specific depthCREDIT — The free tier works out to roughly five prompts a week. It helps to budget the cost of trying something separately from the cost of finishing itPRICE — Rork Max sits on the $200/month Max plan, while regular Rork starts free with paid plans from $25/month
Articles/Dev Tools
Dev Tools/2026-06-14Advanced

Building Rork Subscriptions Around RevenueCat Entitlements — Access Checks, Offering-Driven Paywalls, and Restore

Implementation notes for adding subscriptions to a Rork (Expo) app with RevenueCat. Make Entitlements the single source of truth for access, drive the paywall from Offerings so you can change prices remotely, wire up restore and the customer-info listener, and avoid the sandbox traps — all with working code.

Rork525RevenueCat29Subscriptions15Expo156Monetization37

Premium Article

When you finally have a working app out of Rork and decide to add payments, the first place most people stall isn't the SDK call — it's the question of where, and based on what, do I decide whether someone is a paying member? Do you check the purchased product ID, store a receipt, write a "purchased" flag on the device? Leave that vague and you'll pay for it later: change a price and the check breaks, or a user who switched phones writes in saying "I paid and it's gone."

The real value of dropping in RevenueCat isn't that "subscriptions take a few lines." It's that you can anchor your access check to an Entitlement rather than to product IDs. This article assumes the Expo (React Native) app that regular Rork generates, and walks through an Entitlement-centered design, an Offering-driven paywall, restore, and the sandbox issues I actually hit — with working code. I'll spend less time on the product-setup screens and more on the structure that pays off down the road.

Why "check the product ID" falls apart later

The common first move is to use the purchased product ID (com.example.app.pro_monthly) directly as the access check. It works — until operations begin. Start selling monthly and annual and now you if against both. Raise prices by cutting a new product ID and you add code to keep grandfathered subscribers. Name your iOS and Android product IDs differently and the platform branching doubles. Before long, the function that answers "may I unlock Pro?" is a growing list of product-ID comparisons.

RevenueCat's Entitlement abstracts that list away. In the dashboard you create one Entitlement — say pro — and attach "which products grant pro" to it. The app never knows a single product ID; it only asks whether pro is active. Add products or change prices and the attachment is dashboard work; your app code doesn't move. Whether you nail this down first determines, in my experience, how much the next six months of maintenance costs.

Pin the access axis to exactly one thing. "Is the Entitlement active" is the only truth for access; any device flag or product ID is at most a hint. Build the whole codebase on that premise.

Write the service layer around the Entitlement

Install the SDK first. Because billing pulls in native modules, it does not run in Expo Go — you need a development build (expo-dev-client) or EAS Build. Testing in Expo Go and wondering why the purchase call never fires is the very first trap, so I'm putting it up front.

npx expo install react-native-purchases
# Make a dev build — billing does not work in Expo Go
npx expo prebuild
eas build --profile development --platform ios

The package is react-native-purchases (you'll see an older scoped spelling around; this is the current one). Write the service layer as a thin window that returns Entitlement state, so the rest of the app never thinks about "products" or "receipts."

// src/services/purchases.ts
import Purchases, {
  CustomerInfo,
  LOG_LEVEL,
} from 'react-native-purchases';
import { Platform } from 'react-native';
 
// Decide on ONE entitlement for gating. No product IDs appear here.
export const ENTITLEMENT_ID = 'pro';
 
const API_KEY = Platform.select({
  ios: process.env.EXPO_PUBLIC_RC_IOS_KEY ?? '',
  android: process.env.EXPO_PUBLIC_RC_ANDROID_KEY ?? '',
}) as string;
 
export async function configurePurchases() {
  if (__DEV__) Purchases.setLogLevel(LOG_LEVEL.DEBUG);
  // Without an appUserID, RevenueCat assigns an anonymous one.
  // If you have your own auth, the right move is logIn() AFTER sign-in,
  // not passing an ID to configure() — that double-runs the anon→known alias.
  await Purchases.configure({ apiKey: API_KEY });
}
 
// The single access check. Nothing else decides premium access.
export function isPro(info: CustomerInfo): boolean {
  return info.entitlements.active[ENTITLEMENT_ID] !== undefined;
}
 
export async function getCustomerInfo(): Promise<CustomerInfo> {
  return Purchases.getCustomerInfo();
}

The key is that isPro takes a CustomerInfo and holds no internal state — it's a pure function. If your check depends on some global variable, the value drifts between "just purchased," "just restored," and "just launched." Always phrase it as "ask, passing the freshest CustomerInfo I have," and it lines up cleanly with the listener below.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Use a single Entitlement as the only access check so price changes and platform differences don't break gating
An Offering-driven paywall that reads packages remotely instead of hardcoding prices
The restore flow, the CustomerInfo listener, and the sandbox issues that actually cost you time
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-25
Why Paying Members See a Paywall in Airplane Mode — Keeping RevenueCat Entitlements Alive Offline
Open the app on a weak connection and a paying subscriber sees a paywall flash for a second. Here is how RevenueCat's customerInfo wavers on an offline launch, and a cache design that keeps entitlements valid with a trust window — written as working code for an Expo app.
Dev Tools2026-06-15
The Day a Third Reason to Hide Ads Appeared — Folding Rork App Ad-Free Logic Into One Place
Ads show only on one screen for paying users, or ads never show for free users. The usual cause is that the condition for hiding ads is scattered across the code. Here is how I fold three reasons — subscription, lifetime purchase, and a timed reward unlock — into a single state and route every ad through one hook, written as an implementation note from running six apps as an indie developer.
Dev Tools2026-06-16
I Initialized Ads Before Restoring Purchases, and Paying Users Saw a Banner Flash — Cold-Start Ordering for Rork (Expo) Apps
Consent, ATT, ad SDK init, purchase restore, and remote config all try to run in the same few hundred milliseconds at launch. Get the order wrong and a paying user sees a banner flash, or measurement fires before consent in the EEA. Here is how I fold a Rork-generated Expo app's startup into a single orchestrator and kill the races by design.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →