Skip to main content
On React Native you own RevenueCat, and FlowPilot reads through it. You configure react-native-purchases as you normally would, register a one-line adapter, then present a paywall session exactly like any other flow. This page assumes you already have the FlowPilot Expo SDK installed and configured.
Before you startPaywalls build on the standard FlowPilot setup, plus RevenueCat:
  • A FlowPilot workspace, an app inside it, and an SDK API key for that app.
  • A RevenueCat account with your products, entitlements, and an offering configured for your app.
  • Paywalls enabled for your workspace. If you do not see a Paywalls item in the dashboard sidebar, reach out and we will turn it on.

Install RevenueCat

RevenueCat is an optional peer dependency of the FlowPilot SDK. Install it in any app that shows paywalls.
npx expo install react-native-purchases
npm install react-native-purchases
Paywall support is in FlowPilot Expo SDK 1.1.1 and later.
Expo Go cannot run real purchases. react-native-purchases is a native module, so a purchase needs a development build (npx expo prebuild or an EAS dev build), not Expo Go. In Expo Go the paywall still renders, but purchase and restore are unavailable. Build a dev client to test the full flow.

Wire it up

Configure RevenueCat first, then FlowPilot, then register the adapter before presenting a paywall.
1

Configure RevenueCat (host-owned)

Configure RevenueCat yourself, with your own API key and user identity. FlowPilot never configures RevenueCat and never changes who is signed in.
import Purchases from 'react-native-purchases';

Purchases.configure({ apiKey: 'appl_XXXXXXXXXXXX' });
2

Configure FlowPilot

import { FlowPilot } from '@flowpilotjs/react-native-sdk';

FlowPilot.configure({
  apiKey: 'fp_live_xxxxxxxxxxxxxxxx',
  appId: 'your-app-id',
});
3

Register the adapter

Hand FlowPilot the RevenueCat adapter. This is the only monetization-specific setup step.
import { FlowPilotRevenueCatProvider } from '@flowpilotjs/react-native-sdk';

FlowPilot.registerPurchaseProvider(new FlowPilotRevenueCatProvider());

Present a paywall

On the Expo SDK you present a flow declaratively: resolve a session, then render it with <FlowPilotPresenter />. A paywall works the same way, and its result carries a paywallOutcome.
import { useEffect, useState } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import {
  FlowPilot,
  FlowPilotPresenter,
  type FlowSession,
} from '@flowpilotjs/react-native-sdk';

export function PaywallGate() {
  const [session, setSession] = useState<FlowSession | null>(null);
  const insets = useSafeAreaInsets();

  useEffect(() => {
    // Non-throwing: returns null when no paywall is presentable (fail-open).
    FlowPilot.resolveSession('post_onboarding_paywall').then(setSession);
  }, []);

  if (!session) return <MyFeature />; // nothing to present, continue

  return (
    <FlowPilotPresenter
      session={session}
      safeAreaInsets={insets}
      onComplete={(result) => {
        switch (result.paywallOutcome) {
          case 'purchased':
          case 'restored':
          case 'skippedEntitled':
            unlockFeature();
            break;
          case 'dismissed':
            if (result.paywallGatingMode === 'soft') unlockFeature();
            break;
          default:
            unlockFeature(); // fail-open on every skip or failure
        }
        setSession(null);
      }}
      fallback={<MyFeature />}
    />
  );
}
Do not use FlowPilot.presentPlacement() on the Expo SDK. It is deprecated and renders nothing. Resolve a session and render <FlowPilotPresenter /> as above. See Presenting placements.

The paywall outcome

result.paywallOutcome is one of purchased, restored, dismissed, skippedEntitled, skippedNoFlow, skippedAudience, skippedHoldout, or failed, and is absent for non-paywall flows. result.paywallGatingMode tells you whether a dismissed paywall was soft (continue) or hard (your app keeps the feature locked). The base outcome (completed, dismissed, error) is unchanged, so existing handling still works. See Results and outcomes.

Fail-open by default

If no paywall is configured, the audience does not match, the user is in a holdout, or the flow fails to load, resolveSession returns null and your own UI runs. A paywall problem never blocks your app. When a feature must stay locked, your app enforces that using the outcome.

Test before you ship

Real purchases require a native development build, not Expo Go. In development, the RevenueCat Test Store is the fastest end-to-end path, and an Apple sandbox account covers renewal and restore testing. Run one pass on a real device before submitting.

Diagnostics

The SDK logs each purchase state transition. Raise the log level to see them, and opt out of diagnostic reporting if you prefer:
FlowPilot.configure({
  apiKey: 'fp_live_xxxxxxxxxxxxxxxx',
  appId: 'your-app-id',
  logLevel: 'debug',            // see purchase and offering breadcrumbs
  disableErrorReporting: false, // set true to opt out of scoped error reports
});
Diagnostic reports carry error codes and FlowPilot identifiers only, never RevenueCat keys, prices, or user data.

Common mistakes

  • Testing in Expo Go. Purchases need a native dev build. Expo Go renders the paywall but cannot buy or restore.
  • Registering the provider before configuring RevenueCat. Configure react-native-purchases first; the adapter reads through it.
  • Using presentPlacement. It is a no-op on Expo. Resolve a session and render the presenter.
  • Treating a skip as an error. Every skipped* outcome is a normal, fail-open result.