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

# SDK Quickstart (React Native)

> Integrate Helium into your React Native App.

<Tip>
  To get a better understanding of how paywalls are served, visit [here](/guides/how-does-helium-work).
</Tip>

# Present a Paywall

### Install the SDK

Install the SDK using your preferred package manager:

<CodeGroup>
  ```bash Expo 52+ theme={null}
  npx expo install expo-helium
  ```

  ```bash Older Expo / Bare theme={null}
  npm install @tryheliumai/paywall-sdk-react-native
  # or
  yarn add @tryheliumai/paywall-sdk-react-native

  # [Bare only] then run the following to install the native dependencies
  npx react-native link @tryheliumai/paywall-sdk-react-native
  ```
</CodeGroup>

<Tip>
  We support Expo 49+ but recommend using Helium with **Expo 53+**.
</Tip>

<Warning>
  **The Older Expo / Bare SDK supports iOS only**.

  The Expo 52+ SDK supports both iOS and Android.
</Warning>

### Initialize Helium

<Tip>
  Find your API key [here](https://app.tryhelium.com/profile). If you do not have a Helium account set up yet, you can still integrate but will not be able to show a real paywall.
</Tip>

Initialize Helium by calling `initialize()` early in your app's lifecycle, typically in your root component:

<CodeGroup>
  ```tsx Expo 52+ theme={null}
  import { initialize } from 'expo-helium';

  function App() {
    const asyncHeliumInit = async () => {
      await initialize({
        apiKey: '<your-helium-api-key>',
      });
    };

    useEffect(() => {
      void asyncHeliumInit();
    }, []);
  }
  ```

  ```tsx Older Expo / Bare theme={null}
  import { initialize } from '@tryheliumai/paywall-sdk-react-native';

  function App() {
    const asyncHeliumInit = async () => {
      await initialize({
        apiKey: '<your-helium-api-key>',
      });
    };

    useEffect(() => {
      void asyncHeliumInit();
    }, []);
  }
  ```
</CodeGroup>

### Show Your First Paywall 🎉

<Tip>
  Set up a trigger and workflow in the [dashboard](https://app.tryhelium.com/workflows) to show your desired paywall.
</Tip>

Call `presentUpsell` wherever you want to show a full-screen paywall:

<CodeGroup>
  ```tsx Expo 52+ theme={null}
  import { presentUpsell } from 'expo-helium';

  function YourComponent() {
    const handlePremiumPress = () => {
      presentUpsell({
        triggerName: 'premium_feature_press',
      });
    };

    return (
      <Button title="Try Premium" onPress={handlePremiumPress} />
    );
  }
  ```

  ```tsx Older Expo / Bare theme={null}
  import { presentUpsell } from '@tryheliumai/paywall-sdk-react-native';

  function YourComponent() {
    const handlePremiumPress = () => {
      presentUpsell({
        triggerName: 'premium_feature_press',
      });
    };

    return (
      <Button title="Try Premium" onPress={handlePremiumPress} />
    );
  }
  ```
</CodeGroup>

<ResponseField name="PresentUpsellParams" type="type">
  <Expandable title="properties">
    <ResponseField name="triggerName" type="string" required>
      Pass in the trigger for the workflow you configured in the dashboard.
    </ResponseField>

    <ResponseField name="eventHandlers" type="PaywallEventHandlers?">
      *(Optional)* Handlers that allow you to respond to open, close, dismiss, purchase, open-fail, and custom paywall action [events](/guides/helium-events).
    </ResponseField>

    <ResponseField name="customPaywallTraits" type="Record<string, any>?">
      *(Optional)* Custom key/value pairs that you want available to the paywall.
    </ResponseField>

    <ResponseField name="dontShowIfAlreadyEntitled" type="boolean?" default="false">
      *(Optional)* If `true`, the paywall will not be shown if the user already has an entitlement for a product in the paywall.
    </ResponseField>

    <ResponseField name="onEntitled" type="(() => void)?">
      *(Optional)* Called upon purchase success or purchase restore. If `dontShowIfAlreadyEntitled` is true, this handler will also be called when the paywall is not shown to users who already have entitlement for a product in the paywall.
    </ResponseField>

    <ResponseField name="onPaywallUnavailable" type="(() => void)?">
      *(Optional)* Called if desired paywall and fallback paywall did not show for any reason. This is uncommon, but best practice to handle it just in case. See [Fallback Paywalls](/guides/fallback-bundle).
    </ResponseField>
  </Expandable>
</ResponseField>

You should now be able to see Helium paywalls in your app! Well done! 🎉

<Info>
  Looking for alternative presentation methods? Check out the guide on [Ways to Show a Paywall](/guides/ways-to-show-paywall).
</Info>

# Recommended Setup

Here are some common additional steps that you may want to consider.

### Identifying Users

Identifying users is optional but can help with targeting and when forwarding events to external analytics platforms. If you are not sure, you probably do not need to identify your users.

<Tip>
  Identify users as early as you can to maximize consistency in metrics and targeting. Ideally in your `initialize` call!
</Tip>

<CodeGroup>
  ```tsx Set during initialize theme={null}
  await initialize({
    apiKey: '<your-helium-api-key>',
    // (Optional) Custom user id, e.g. your amplitude analytics user id.
    customUserId: '<your-custom-user-id>',
    // (Optional) Custom user traits
    customUserTraits: {
      "example_trait": "example_value",
    },
  });
  ```

  ```tsx Update after initialize theme={null}
  import { setCustomUserId } from 'expo-helium';

  setCustomUserId('your-custom-user-id');
  ```
</CodeGroup>

### Helium Events

Helium dispatches various events during paywall presentation and purchase flow. You can optionally handle these events in your mobile app. You can also configure Helium to forward them to your [existing analytics provider](/guides/third-party-analytics).

#### PaywallEventHandlers

When displaying a paywall you can pass in event handlers to listen for select events:

```tsx theme={null}
presentUpsell({
  triggerName: 'my_paywall',
  eventHandlers: {
    onOpen: (event) => {
      console.log(`${event.type}`)
    },
    onClose: (event) => {
      console.log(`${event.type}`)
    },
    onPurchaseSucceeded: (event) => {
      console.log(`${event.type}`)
    },
    onDismissed: (event) => {
      console.log(`${event.type}`)
    },
    onOpenFailed: (event) => {
      console.log(`${event.type}`)
    },
    onCustomPaywallAction: (event) => {
      console.log(`${event.type}`)
    },
    onAnyEvent: (event) => {
      // A handler for all paywall-related events.
      // Note that if you have other handlers (i.e. onOpen) set up,
      // both that handler AND this one will fire during paywall open.
    },
  },
});
```

#### Global Event Listener

You can also listen for all Helium events globally by passing `onHeliumPaywallEvent` to `initialize`:

```tsx theme={null}
await initialize({
  apiKey: '<your-helium-api-key>',
  onHeliumPaywallEvent: (event) => {
    switch (event.type) {
      case 'paywallOpen':
        break;
      case 'purchaseSucceeded':
        // Handle successful purchase
        break;
      // handle other events as desired
    }
  },
});
```

### Fallback Paywalls

It is **highly recommended** that you set up "fallbacks" to handle the rare case when a paywall fails to display. Please follow [this guide](/guides/fallback-bundle) to do so.

<Tip>
  Do this *after* you have a paywall created that you want to use in production.
</Tip>

### Checking Subscription Status & Entitlements

<Tip>
  Check entitlements before showing paywalls to avoid showing a paywall to a user who should not see it.
</Tip>

<CodeGroup>
  ```typescript Use dontShowIfAlreadyEntitled with presentUpsell theme={null}
  presentUpsell({
    triggerName: 'my_paywall',
    dontShowIfAlreadyEntitled: true
  });
  ```

  ```typescript Check before showing paywall theme={null}
  const hasActiveSubscription = await hasAnyActiveSubscription();
  if (hasActiveSubscription) {
    featureGatedLogic(); // replace this with your logic
  } else {
    presentUpsell({
      triggerName: 'my_paywall',
      eventHandlers: {
        onPurchaseSucceeded: (event: PurchaseSucceededEvent) => {
          featureGatedLogic(); // replace this with your logic
        },
      },
    });
  }
  ```
</CodeGroup>

<Accordion title="All Entitlement Helper Methods">
  ```typescript theme={null}
  /**
   * Checks if the user has any active subscription (including non-renewable)
   */
  export const hasAnyActiveSubscription = async (): Promise<boolean>;

  /**
   * Checks if the user has any entitlement
   */
  export const hasAnyEntitlement = async (): Promise<boolean>;

  /**
   * Checks if the user has an active entitlement for any product attached to
   * the paywall that will show for the provided trigger.
   * Returns undefined if not known (i.e. the paywall is not downloaded yet).
   */
  export const hasEntitlementForPaywall = async (trigger: string): Promise<boolean | undefined>;
  ```
</Accordion>

# Advanced

### RevenueCat

<Note>
  By default, Helium will handle purchases for you! This section is typically for users who already use RevenueCat in their app.
</Note>

Helium integrates seamlessly with RevenueCat so you can continue to let RevenueCat handle your purchases and entitlements.

<Note>
  If you haven't already, make sure to [install](https://www.revenuecat.com/docs/getting-started/installation/expo) RevenueCat (for non-Expo see [here](https://www.revenuecat.com/docs/getting-started/installation/reactnative)).

  Make sure to initialize RevenueCat (`Purchases.configure()`) before initializing Helium.
</Note>

Configure Helium to use RevenueCat by passing `createRevenueCatPurchaseConfig` to `initialize`:

<CodeGroup>
  ```tsx Expo 52+ theme={null}
  import { initialize } from 'expo-helium';
  import { createRevenueCatPurchaseConfig } from "expo-helium/src/revenuecat";

  const asyncHeliumInit = async () => {
    await initialize({
      apiKey: '<your-helium-api-key>',
      purchaseConfig: createRevenueCatPurchaseConfig(),
      revenueCatAppUserId: await Purchases.getAppUserID()
    });
  };

  useEffect(() => {
    void asyncHeliumInit();
  }, []);
  ```

  ```tsx Older Expo / Bare theme={null}
  import { initialize } from '@tryheliumai/paywall-sdk-react-native';
  import { createRevenueCatPurchaseConfig } from "@tryheliumai/paywall-sdk-react-native/src/revenuecat";

  const asyncHeliumInit = async () => {
    await initialize({
      apiKey: '<your-helium-api-key>',
      purchaseConfig: createRevenueCatPurchaseConfig(
        // (optional) Set if you want Helium to initialize RevenueCat for you.
        // Otherwise initialize RevenueCat (`Purchases.configure()`) before initializing Helium
        { apiKey: 'revenue_cat_api_key' }
      ),
      revenueCatAppUserId: await Purchases.getAppUserID()
    });
  };

  useEffect(() => {
    void asyncHeliumInit();
  }, []);
  ```
</CodeGroup>

#### RevenueCat appUserID

If you ever [change the appUserID](https://www.revenuecat.com/docs/customers/identifying-customers#logging-in-after-configuration) of a user, keep Helium in sync with:

```tsx theme={null}
import { setRevenueCatAppUserId } from 'expo-helium';

setRevenueCatAppUserId(await Purchases.getAppUserID());
```

### Custom Purchase Handling

<Note>
  By default, Helium will handle purchases for you! This section is for those who want to implement custom purchase logic.
</Note>

Pass a custom purchase config to `initialize`:

<CodeGroup>
  ```tsx Expo 52+ theme={null}
  import { createCustomPurchaseConfig } from 'expo-helium';

  // In your initialize call:
  purchaseConfig: createCustomPurchaseConfig({
    makePurchaseIOS: async (productId) => {
      // Your purchase logic here
      // Return a HeliumTransactionStatus
      return { status: 'purchased' };
    },
    makePurchaseAndroid: async (productId) => {
      // Your purchase logic here
      // Return a HeliumTransactionStatus
      return { status: 'purchased' };
    },
    restorePurchases: async () => {
      // Your restore logic here
      return true;
    }
  }),
  ```

  ```tsx Older Expo / Bare theme={null}
  import { createCustomPurchaseConfig } from '@tryheliumai/paywall-sdk-react-native';

  // In your initialize call:
  purchaseConfig: createCustomPurchaseConfig({
    makePurchase: async (productId) => {
      // Your purchase logic here
      // Return a HeliumTransactionStatus
      return { status: 'purchased' };
    },
    restorePurchases: async () => {
      // Your restore logic here
      return true;
    }
  }),
  ```
</CodeGroup>

```tsx theme={null}
type HeliumTransactionStatus =
  'purchased' | 'failed' | 'cancelled' | 'pending' | 'restored';
```

### Additional Features

<Note>
  For the full public API and detailed parameter documentation, see the inline docstrings in the SDK source. Import Helium in your project and use your IDE's autocomplete or jump-to-definition to explore all available methods and types.
</Note>

<Accordion title="Hiding Paywalls Programmatically">
  You can programmatically hide paywalls using:

  ```typescript theme={null}
  // Hide the current paywall
  hideUpsell();

  // Hide all currently displayed paywalls
  hideAllUpsells();
  ```
</Accordion>

<Accordion title="Reset Helium">
  Reset Helium entirely so you can call initialize again, for example after changing user traits that can affect the paywalls a user might see via targeting.

  ```typescript theme={null}
  import { resetHelium } from 'expo-helium';

  await resetHelium();
  ```
</Accordion>

### Expo Development Build

Please note that the Helium SDK uses native code, so you must create a [development build](https://docs.expo.dev/develop/development-builds/introduction/). A common command to run for this is:

```bash theme={null}
npx expo run:ios # or npx expo run:ios --device
```

### Troubleshooting

#### Check if the Helium SDK is installed

<CodeGroup>
  ```bash Expo 52+ theme={null}
  [ -d "node_modules/expo-helium" ] && echo "✅ expo-helium package found in node_modules" || echo "❌ expo-helium package NOT found in node_modules"
  ```

  ```bash Older Expo / Bare theme={null}
  [ -d "node_modules/@tryheliumai/paywall-sdk-react-native" ] && echo "✅ paywall-sdk-react-native package found in node_modules" || echo "❌ paywall-sdk-react-native package NOT found in node_modules"
  ```
</CodeGroup>

If the package is not found, install it again:

<CodeGroup>
  ```bash Expo 52+ theme={null}
  npx expo install expo-helium
  ```

  ```bash Older Expo / Bare theme={null}
  npm install @tryheliumai/paywall-sdk-react-native
  # or
  yarn add @tryheliumai/paywall-sdk-react-native

  # then run the following to install the native dependencies
  npx react-native link @tryheliumai/paywall-sdk-react-native
  ```
</CodeGroup>

#### Check if Helium is properly installed

To verify that the Helium pod is correctly installed in your project, run:

<CodeGroup>
  ```bash Expo 52+ theme={null}
  grep -E "Helium" ios/Podfile.lock > /dev/null && echo "✅ Helium found in ios/Podfile.lock" || echo "❌ Helium not found in ios/Podfile.lock" && grep -E "HeliumPaywallSdk" ios/Podfile.lock > /dev/null && echo "✅ HeliumPaywallSdk found in ios/Podfile.lock" || echo "❌ HeliumPaywallSdk not found in ios/Podfile.lock"
  ```

  ```bash Older Expo / Bare theme={null}
  grep -E "Helium" ios/Podfile.lock > /dev/null && echo "✅ Helium found in ios/Podfile.lock" || echo "❌ Helium not found in ios/Podfile.lock"
  ```
</CodeGroup>

If not found, try these commands:

```bash theme={null}
# regenerate the ios (and android) directories
npx expo prebuild --clean

# run a development build
npx expo run:ios # or npx expo run:ios --device
```
