> ## Documentation Index
> Fetch the complete documentation index at: https://helium-figma-import-quickguide.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Quickstart (React Native)

> Integrate Helium into your React Native App.

## Background

Get set up with the Helium SDK for React Native. Reach out over your Helium slack channel or email [founders@tryhelium.com](mailto:founders@tryhelium.com) for any questions.

<Warning>
  The Helium React Native SDK **currently supports iOS only. Android support is expected mid- to late-November.**
</Warning>

## Installation

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 and up**. If you're on an older version and having issues, ping us - we've got experience with all kinds of versioning, upgrade, and custom build plugin work.
</Tip>

## Configuration

Follow these steps to integrate the SDK.

### Initialization

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

<Note>
  If you are using **RevenueCat**, follow the example in the next section.
</Note>

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

  function App() {
    const asyncHeliumInit = async () => {
      await initialize({
        apiKey: '<your-helium-api-key>',
        // Fallback bundle is highly recommended. See Fallbacks section of this page.
        // fallbackBundle: require('./assets/fallback-bundle-xxxx-xx-xx.json'),
      });
    };

    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>',
        // Fallback bundle is highly recommended. See Fallbacks section of this page.
        // fallbackBundle: require('./assets/fallback-bundle-xxxx-xx-xx.json'),
      });
    };

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

#### Use RevenueCat with Helium

<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)).
</Note>

<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(
        // (optional) Set if you want Helium to initialize RevenueCat for you.
        // Otherwise initialize RevenueCat (`Purchases.configure()`) before initializing Helium
        { apiKey: 'revenue_cat_api_key' }
      ),
      // Fallback bundle is highly recommended. See Fallbacks section of this page.
      // fallbackBundle: require('./assets/fallback-bundle-xxxx-xx-xx.json'),
      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' }
      ),
      // Fallback bundle is highly recommended. See Fallbacks section of this page.
      // fallbackBundle: require('./assets/fallback-bundle-xxxx-xx-xx.json'),
      revenueCatAppUserId: await Purchases.getAppUserID()
    });
  };

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

#### Custom Purchase Config

To use your own purchase logic, pass in a custom purchase config to `initialize`:

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

  // In your initialize call:
  purchaseConfig: createCustomPurchaseConfig({
    // NOTE - on version 3.1.0+ makePurchase is deprecated in favor of
    // makePurchaseIOS and makePurchaseAndroid
    makePurchase: 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';
```

### Custom User ID and Custom User Traits

You can pass in a custom user ID and custom user traits if desired to `initialize`:

```tsx theme={null}
// (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",
},
```

## Presenting Paywalls

<Note>
  You must have a trigger and workflow configured in the [dashboard](https://app.tryhelium.com/workflows) in order to show a paywall.
</Note>

Use the `presentUpsell` method to present a paywall. `presentUpsell` takes in `PresentUpsellParams`:

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

    <ResponseField name="onFallback" type="() => void">
      This will be called when paywall fails to show due to an unsuccessful paywall download or if an invalid trigger is provided. (See Fallbacks section for more details.)
    </ResponseField>

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

    <ResponseField name="customPaywallTraits" type="Record<string, any>">
      (Advanced) 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 show if the user already has entitlements for any product in the paywall. See the Entitlements section of this guide for more details.
    </ResponseField>
  </Expandable>
</ResponseField>

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

  function YourComponent() {
    const handlePremiumPress = () => {
      presentUpsell({
        triggerName: 'premium_feature_press',
        onFallback: () => {
          // Implement logic to open a default paywall or display error dialog
          console.log('[Helium] onFallback called!');
        }
      });
    };

    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',
        onFallback: () => {
          // Implement logic to open a default paywall or display error dialog
          console.log('[Helium] onFallback called!');
        }
      });
    };

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

### PaywallEventHandlers

You can pass in paywall event handlers to listen for specific paywall events. (See the next section Helium Events for a way to handle global events).

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

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

## Helium Events

Listen for all [Helium Events](/sdk/helium-events) by implementing `onHeliumPaywallEvent` and passing it to initialize:

```tsx theme={null}
onHeliumPaywallEvent: (event) => {
  switch (event.type) {
    case 'paywallOpen':
      break;
    case 'purchaseSucceeded':
      // Handle successful purchase
      break;
    // handle other events as desired
  }
},
```

## Checking Subscription Status & Entitlements

Use these methods to check current user subscription status.

```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 provided trigger.
 * @param trigger The trigger name to check entitlement for
 * @returns Promise resolving to true if entitled, false if not, or undefined if not known (i.e. the paywall is not downloaded yet)
 */
export const hasEntitlementForPaywall = async (trigger: string): Promise<boolean | undefined>;
```

#### Example Usage

<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',
      onFallback: () => {
        // handle fallback logic in case paywall fails to show (should be quite rare)
      },
      eventHandlers: {
        onPurchaseSucceeded: (event: PurchaseSucceededEvent) => {
          featureGatedLogic(); // replace this with your logic
        },
      },
    });
  }
  ```
</CodeGroup>

## Fallbacks

There are currently two ways you can handle a "fallback" situation in the rare case that a paywall fails to download or an invalid trigger is provided. You can also do both. Better to be prepared!

### 1. Use the `onFallback` function

You can pass this in when you call `presentUpsell` and handle however you want.

### 2. Provide a fallback bundle (recommended)

Provide a fallback bundle as described in [this guide](/guides/fallback-bundle).

<Tip>
  Update your fallback bundle whenever you make changes to your paywall for maximum reliability.
</Tip>

## Loading Budgets

If a paywall has not completed downloading when you attempt to present it, a loading state can show.

By default, Helium will show this loading state as needed (a shimmer view for up to 7 seconds). You can configure, turn off, or set trigger-specific loading budgets.

If the budget expires before the paywall is ready, a fallback paywall will show if available otherwise the loading state will hide and `onFallback` will be called.

```tsx theme={null}
const paywallLoadingConfig: HeliumPaywallLoadingConfig = {
  useLoadingState: true,
  loadingBudget: 4,
  perTriggerLoadingConfig: {
    "onboarding": {
      loadingBudget: 5,
    },
    "trial": {
      useLoadingState: false,
    }
  },
};

await initialize({
  apiKey: "helium-api-key",
  fallbackBundle: require('./assets/fallback-bundle-xxxx-xx-xx.json'),
  paywallLoadingConfig: paywallLoadingConfig, // << pass to initialize
});
```

## Additional Considerations

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