ReadyNative

Adapty

Set up Adapty for payments in an Expo app with ReadyNative: in-app subscriptions + Paywall Builder · dev build required (no Expo Go)

Pro

Expo Go: no - dev build required

Ships native code. Make a dev build with bunx expo run:ios / bunx expo run:android (or bunx eas-cli build --profile development).

This gives you in-app subscriptions through Adapty: src/lib/payments.ts implements the shim on react-native-adapty (its active list holds access-level ids such as premium), a PaymentsProvider (order 70) activates the SDK once and listens on onLatestProfileLoad, and /paywall ships as a modal with your feature bullets, the placement's products with localized price and period, Buy, Restore, and Terms/Privacy links from readynative.config.ts urls. If you set up with --with-examples there's an example at /examples/paywall and a jest mock for the SDK. @/lib/payments keeps the same API whichever option you pick - pick Adapty when you want its no-code Paywall Builder and A/B tests on placements.

Setup

bun run setup --payments adapty
  1. Create an app at app.adapty.io and add the iOS bundle id (from readynative.config.ts app.ios.bundleId, plus .dev for the dev variant) and the Android package under [App settings].
  2. Open [App settings] → [General] and copy the Public SDK key (public_live_…) into .env as EXPO_PUBLIC_ADAPTY_PUBLIC_KEY. It's public and serves both platforms. Without it the module no-ops: useEntitlements() returns { active: [], loading: false } and /paywall shows "Configure Adapty".
  3. Create the subscriptions in [App Store Connect] → [Subscriptions] and [Play Console] → [Monetize] → [Subscriptions], then add them in Adapty under [Products].
  4. Open [Access levels] and use premium (Adapty's built-in default, and what the examples check for), then attach your products to it.
  5. Open [Paywalls], build one with the Paywall Builder, and attach your monthly product to it.
  6. Open [Placements] and attach that paywall to a placement. The default placement id is default; if you use another one, put it in .env as EXPO_PUBLIC_ADAPTY_PLACEMENT_ID.
  7. Add a sandbox tester in [App Store Connect] → [Users and Access] → [Sandbox] → [Testers], and a license tester in [Play Console] → [Setup] → [License testing]. Purchases only work on a real device signed into one of those accounts.
  8. Make a dev build - npx expo run:ios, npx expo run:android, or eas build --profile development. The module also patches the app config with ["expo-build-properties", { ios: { useFrameworks: "dynamic" } }], which the SDK's Swift Package dependencies require.

Going to production?

Make sure every product id exists and is approved in App Store Connect and Play Console - a product that only lives in Adapty never shows a price. Check that the paywall you want live is the one attached to the placement EXPO_PUBLIC_ADAPTY_PLACEMENT_ID reads, because that's the only one the app asks for.

  1. Run bun run doctor - every row for this module should be green.

The keys, in one place:

KeyWhere to get it
EXPO_PUBLIC_ADAPTY_PUBLIC_KEYAdapty → App settings → General → Public SDK key (public_live_…)
EXPO_PUBLIC_ADAPTY_PLACEMENT_IDAdapty → Placements → the placement your paywall is attached to (default default)

Dashboards: App settings · Products · Paywalls · Placements · App Store Connect sandbox testers · Play Console license testers. Deps: react-native-adapty ^4.1.0 (RN ≥ 0.75, one package for the SDK and the Paywall Builder UI) and expo-build-properties.

Usage

Gate a feature on an access level:

import { payments } from "@/lib/payments";

const { active, loading } = payments.useEntitlements(); // active: ["premium"]
if (!loading && !active.includes("premium")) await payments.presentPaywall?.();

Sell from your own screen:

import { getPaywallFlow, getPaywallProducts, purchaseProduct } from "@/lib/payments";

const flow = await getPaywallFlow();
const products = await getPaywallProducts(flow);
const bought = await purchaseProduct(products[0]); // false when the user cancels or the purchase is pending

Restore, which the stores require you to offer:

import { payments } from "@/lib/payments";

await payments.restorePurchases();

presentPaywall() shows the no-code paywall when the flow has a view configuration; a remote-config-only paywall opens /paywall, which lists the placement's products instead.

Gotchas

  • Expo Go can't run this module - native StoreKit and Play Billing need a dev build. In Expo Go the SDK falls back to its mock mode: no real products, no purchases.
  • expo-build-properties ios.useFrameworks: "dynamic" applies to the whole iOS project. If another native library refuses dynamic frameworks, follow its docs (usually a use_frameworks! :linkage => :dynamic exception) - Adapty SDK 4.x needs the setting.
  • react-native-adapty has no JS implementation of the bridge under jest. The root __mocks__ file is picked up automatically - don't add jest.mock() for it.
  • adapty.activate() throws when called twice, so the provider passes __ignoreActivationOnFastRefresh in dev.
  • adapty.activate() also passes ipAddressCollectionDisabled: !features.tracking, ios: { idfaCollectionDisabled: !features.tracking } and android: { adIdCollectionDisabled: !features.tracking }, so Adapty collects no IDFA, GAID or IP address unless features.tracking is on in readynative.config.ts.
  • presentPaywall() resolves once the view is presented, not when it closes; entitlements arrive through onLatestProfileLoad or the view's onPurchaseCompleted handler.

Check it works (the Examples steps need a tree set up with --with-examples):

  1. Set up the dashboard as above: app, bundle id and package, products, the premium access level, and a paywall with a monthly product attached to the placement.
  2. Put EXPO_PUBLIC_ADAPTY_PUBLIC_KEY in .env and run npx expo run:ios on a real device signed into a sandbox Apple ID (Settings → App Store → Sandbox Account).
  3. Open Examples → "Paywall & entitlements" and expect "none active".
  4. Tap "Open paywall": the Paywall Builder paywall appears (or /paywall when the paywall has no builder view) with the product and a localized price. Buy → sandbox sheet → confirm → the sheet closes with a toast "Purchase complete" and access levels show premium.
  5. Delete and reinstall the app, then tap "Restore purchases": access levels show premium again.
  6. Repeat on Android with a license-tester Google account.

Remove it

While modules/ exists (a tree set up with --keep-modules), setup does all of it:

bun run setup --payments none --yes --keep-modules

In a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:

  1. Delete the files that are still there (the demo screens are gone already unless you set up with --with-examples): __mocks__/react-native-adapty.ts, src/app/examples/paywall.tsx, src/app/paywall/_layout.tsx, src/app/paywall/index.tsx, src/lib/__tests__/payments-adapty.test.ts, src/screens/examples/paywall-example-screen.tsx, src/screens/paywall/paywall-screen.tsx.
  2. Replace, don't delete src/lib/payments.ts: core code imports it, so swap in the no-op version from modules/payments/none/files/ of a fresh clone of your tier repo - same exports, nothing behind them.
  3. Uninstall the dependencies: bun remove expo-build-properties react-native-adapty.
  4. Drop the config plugin expo-build-properties from .readynative.json → modules.app.expo.plugins (that is where app.config.ts reads it from), then rebuild the dev build.
  5. Unwrap the provider: delete <PaymentsProvider> and its import from src/providers.tsx.
  6. Remove the env keys EXPO_PUBLIC_ADAPTY_PUBLIC_KEY, EXPO_PUBLIC_ADAPTY_PLACEMENT_ID from .env, .env.example and your EAS environment, and EXPO_PUBLIC_ADAPTY_PUBLIC_KEY, EXPO_PUBLIC_ADAPTY_PLACEMENT_ID from src/lib/env.ts.
  7. Update the privacy declarations: remove this module's entries from .readynative.json → modules.app.expo.ios.privacyManifests, then re-run bun run gen:privacy and revise your store privacy answers.
  8. Check it: bun run typecheck and bun run lint point at anything that still imports the removed files; bun run gen:graph refreshes docs/ARCHITECTURE.md.

Reference

Everything below is generated from modules/payments/adapty/module.json - the same file bun run setup reads, so it is what actually lands in your repo.

Install

bun run setup --payments adapty

Module id: payments/adapty.

Dependencies

PackageVersionKind
expo-build-properties~57.0.17dependency
react-native-adapty^4.1.0dependency

Config plugins

Merged into app.config.ts through .readynative.json (modules.app):

  • expo-build-properties (with options)

Environment keys

KeyRequiredServer-onlyExampleDocs
EXPO_PUBLIC_ADAPTY_PUBLIC_KEYyesnopublic_live_xxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxdashboard
EXPO_PUBLIC_ADAPTY_PLACEMENT_IDnonodefaultdashboard

Keys go in .env (see .env.example). Required keys are checked by bun run doctor; Server-only keys have no EXPO_PUBLIC_ prefix, are read only by API routes and never reach the bundle.

Privacy

Play Data safety draft: collects Purchase history, Profile id (anonymous by default), Device info; shared with Adapty (processor). Source of truth: vendor disclosure.

Apple privacy manifest data types (composed into ios.privacyManifests by setup):

TypeLinked to userTrackingPurposes
PurchaseHistoryyesnoAppFunctionality
UserIDyesnoAppFunctionality

Providers

Rendered in src/providers.tsx (lower order = outermost):

OrderProviderFrom
70PaymentsProvider@/lib/payments

Doctor checks

  • env: EXPO_PUBLIC_ADAPTY_PUBLIC_KEY, EXPO_PUBLIC_ADAPTY_PLACEMENT_ID
  • dev build toolchain (Xcode / Android SDK)

After setup

  1. Adapty: create an app at https://app.adapty.io, add the App Store bundle id and Play package name from readynative.config.ts (App settings → iOS SDK / Android SDK).
  2. Adapty: copy the Public SDK key (App settings → General) into .env as EXPO_PUBLIC_ADAPTY_PUBLIC_KEY (one key serves both platforms).
  3. Adapty: create Products (App Store Connect / Play Console product ids), keep the default access level premium (or rename it in src/lib/payments.ts), build a Paywall and attach it to a Placement; put the placement id into .env as EXPO_PUBLIC_ADAPTY_PLACEMENT_ID (defaults to default).
  4. react-native-adapty is a native module and needs expo-build-properties with ios.useFrameworks: "dynamic" (added to app config by this module): make a dev build with npx expo run:ios / npx expo run:android (or eas build --profile development). Expo Go only runs the SDK's mock mode.

Files

8 files copied to the project root
  • __mocks__/react-native-adapty.ts
  • src/app/examples/paywall.tsx
  • src/app/paywall/_layout.tsx
  • src/app/paywall/index.tsx
  • src/lib/__tests__/payments-adapty.test.ts
  • src/lib/payments.ts
  • src/screens/examples/paywall-example-screen.tsx
  • src/screens/paywall/paywall-screen.tsx

On this page

Get ReadyNative