Skip to main content
To get a better understanding of how paywalls are served, visit here.

Present a Paywall

Install the SDK

Install the SDK using your preferred package manager:
npx expo install expo-helium
We support Expo 49+ but recommend using Helium with Expo 53+.
The Older Expo / Bare SDK supports iOS only.The Expo 52+ SDK supports both iOS and Android.

Initialize Helium

Find your API key here. 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.
Initialize Helium by calling initialize() early in your app’s lifecycle, typically in your root component:
import { initialize } from 'expo-helium';

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

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

Show Your First Paywall πŸŽ‰

Set up a trigger and workflow in the dashboard to show your desired paywall.
Call presentUpsell wherever you want to show a full-screen paywall:
import { presentUpsell } from 'expo-helium';

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

  return (
    <Button title="Try Premium" onPress={handlePremiumPress} />
  );
}
PresentUpsellParams
type
You should now be able to see Helium paywalls in your app! Well done! πŸŽ‰
Looking for alternative presentation methods? Check out the guide on Ways to Show a Paywall.

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.
Identify users as early as you can to maximize consistency in metrics and targeting. Ideally in your initialize call!
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",
  },
});

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.

PaywallEventHandlers

When displaying a paywall you can pass in event handlers to listen for select events:
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:
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 to do so.
Do this after you have a paywall created that you want to use in production.

Checking Subscription Status & Entitlements

Check entitlements before showing paywalls to avoid showing a paywall to a user who should not see it.
presentUpsell({
  triggerName: 'my_paywall',
  dontShowIfAlreadyEntitled: true
});
/**
 * 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>;

Advanced

RevenueCat

By default, Helium will handle purchases for you! This section is typically for users who already use RevenueCat in their app.
Helium integrates seamlessly with RevenueCat so you can continue to let RevenueCat handle your purchases and entitlements.
If you haven’t already, make sure to install RevenueCat (for non-Expo see here).Make sure to initialize RevenueCat (Purchases.configure()) before initializing Helium.
Configure Helium to use RevenueCat by passing createRevenueCatPurchaseConfig to initialize:
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();
}, []);

RevenueCat appUserID

If you ever change the appUserID of a user, keep Helium in sync with:
import { setRevenueCatAppUserId } from 'expo-helium';

setRevenueCatAppUserId(await Purchases.getAppUserID());

Custom Purchase Handling

By default, Helium will handle purchases for you! This section is for those who want to implement custom purchase logic.
Pass a custom purchase config to initialize:
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;
  }
}),
type HeliumTransactionStatus =
  'purchased' | 'failed' | 'cancelled' | 'pending' | 'restored';

Additional Features

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.
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.
import { resetHelium } from 'expo-helium';

await resetHelium();

Expo Development Build

Please note that the Helium SDK uses native code, so you must create a development build. A common command to run for this is:
npx expo run:ios # or npx expo run:ios --device

Troubleshooting

Check if the Helium SDK is installed

[ -d "node_modules/expo-helium" ] && echo "βœ… expo-helium package found in node_modules" || echo "❌ expo-helium package NOT found in node_modules"
If the package is not found, install it again:
npx expo install expo-helium

Check if Helium is properly installed

To verify that the Helium pod is correctly installed in your project, run:
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"
If not found, try these commands:
# regenerate the ios (and android) directories
npx expo prebuild --clean

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