> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Expo integration

> Configure RevenueCat, register the FlowPilot adapter, and present a paywall in a React Native app.

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](/expo-sdk/quickstart).

<Info>
  **Before you start**

  Paywalls build on the standard FlowPilot setup, plus RevenueCat:

  * A FlowPilot [workspace](/dashboard/workspaces-and-apps), an [app](/dashboard/workspaces-and-apps) inside it, and an [SDK API key](/dashboard/api-keys) for that app.
  * A [RevenueCat](https://www.revenuecat.com) 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.
</Info>

## Install RevenueCat

RevenueCat is an optional peer dependency of the FlowPilot SDK. Install it in any app that shows paywalls.

<CodeGroup>
  ```bash Expo theme={null}
  npx expo install react-native-purchases
  ```

  ```bash npm theme={null}
  npm install react-native-purchases
  ```
</CodeGroup>

Paywall support is in FlowPilot Expo SDK **1.1.1** and later.

<Warning>
  **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.
</Warning>

## Wire it up

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

<Steps>
  <Step title="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.

    ```tsx theme={null}
    import Purchases from 'react-native-purchases';

    Purchases.configure({ apiKey: 'appl_XXXXXXXXXXXX' });
    ```
  </Step>

  <Step title="Configure FlowPilot">
    ```tsx theme={null}
    import { FlowPilot } from '@flowpilotjs/react-native-sdk';

    FlowPilot.configure({
      apiKey: 'fp_live_xxxxxxxxxxxxxxxx',
      appId: 'your-app-id',
    });
    ```
  </Step>

  <Step title="Register the adapter">
    Hand FlowPilot the RevenueCat adapter. This is the only monetization-specific setup step.

    ```tsx theme={null}
    import { FlowPilotRevenueCatProvider } from '@flowpilotjs/react-native-sdk';

    FlowPilot.registerPurchaseProvider(new FlowPilotRevenueCatProvider());
    ```
  </Step>
</Steps>

## 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`.

```tsx theme={null}
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 />}
    />
  );
}
```

<Warning>
  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](/expo-sdk/presenting-placements).
</Warning>

### 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](/expo-sdk/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:

```tsx theme={null}
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.

## Related pages

* [Paywalls overview](/paywalls/index)
* [Build a paywall](/paywalls/building-a-paywall)
* [iOS integration](/paywalls/ios)
* [Results and outcomes](/expo-sdk/results-and-outcomes)
* [App Store review](/paywalls/app-review)
