React Native quickstart

Add Voidhash to an Expo or React Native app and show your first paywall.

This guide takes an existing app from installation to a paywall and access check.

Install the SDK

Install the SDK and its native peers:

npm install @voidhash/react-native react-native-nitro-modules @react-native-async-storage/async-storage effect expo-constants expo-linking

The SDK requires react-native-nitro-modules 0.35.x (peer range ^0.35.5). In an Expo project, npx expo install react-native-nitro-modules picks the version that matches your Expo SDK. Every Nitro-based module in the app must resolve to the same Nitro version.

For iOS, install pods after adding the packages:

npx pod-install

Connect your project

Run the interactive initializer from your app's root. The CLI is not a dependency of the SDK — run it directly:

npx voidhash-cli init

pnpm dlx voidhash-cli init works the same way. Install it with --save-dev only if you want the Metro integration, which runs types generate --watch alongside the dev server and needs the binary on PATH. See the CLI reference.

The command signs you in, connects the directory to a Voidhash project, and creates:

  • voidhash.config.ts with your team and project slugs;
  • src/lib/voidhash.ts with the project's publishable key; and
  • voidhash.gen.d.ts with typed product, perk, and paywall-location slugs.

It does not change your dependencies or create a local product schema. Products and paywall locations are managed in Studio.

The SDK uses your app's scheme for purchase callbacks. Expo projects can set it in app.json:

app.json
{
  "expo": {
    "scheme": "myapp"
  }
}

For bare React Native, pass the same value when creating the client:

src/lib/voidhash.ts
import { createVoidhashClient } from "@voidhash/react-native";

export const voidhash = createVoidhashClient("vh_pk_...", {
  scheme: "myapp",
});

Expo Router apps should also prevent callback links from becoming routes:

app/+native-intent.ts
import { expoRouterWithVoidhashCallback } from "@voidhash/react-native";

export function redirectSystemPath(options: { path: string; initial: boolean }) {
  return expoRouterWithVoidhashCallback(options);
}

Configure one test offer in Studio

Create the smallest complete catalog:

  1. Create a perk such as premium.
  2. Create a product, choose its billing duration, and attach the perk.
  3. Create a paywall that includes the product, then publish it.
  4. Create a paywall location such as onboarding and assign the published paywall.

The generated client enables development purchases in debug builds, so store configuration is not required for this first run. Connect App Store or Google Play before testing a release build. See Development purchase mode for fixed prices, isolation, and lifecycle tools.

Run type generation after changing product, perk, or location slugs:

npx voidhash-cli types generate

See Products and perks and Paywalls for the model behind these steps.

Wrap your app

Mount the generated provider once at the app root:

app/_layout.tsx
import { Stack } from "expo-router";

import { voidhash } from "../src/lib/voidhash";

export default function RootLayout() {
  return (
    <voidhash.Provider>
      <Stack />
    </voidhash.Provider>
  );
}

The provider initializes the native store connection, loads the project schema, restores the current identity, and starts observing transactions.

Show the paywall

Resolve the paywall by location and call show():

app/upgrade.tsx
import { Button } from "react-native";

import { voidhash } from "../src/lib/voidhash";

export default function UpgradeScreen() {
  const paywall = voidhash.usePaywallByLocation("onboarding");

  return (
    <Button
      title="View plans"
      onPress={async () => {
        const result = await paywall.show();

        if (result.status !== "shown") {
          // `not_assigned` is the expected case when the location has no
          // published paywall — fall back to your own screen instead of
          // leaving the customer with nothing.
          console.warn("Paywall not shown", result.status);
        }
      }}
    />
  );
}

show() resolves to a ShowPaywallResult that names why a paywall was not presented. See Display a paywall for every status.

The SDK handles product loading, purchase and restore actions, and dismissal for a hosted paywall.

Check access

Gate features with useHasPerk:

const { hasAccess, isLoading } = voidhash.useHasPerk("premium");

if (isLoading) return null;
return hasAccess ? <PremiumContent /> : <UpgradePrompt />;

The person snapshot refreshes after a successful purchase or restore, so React re-renders with the new grant. See Check access for offline behavior and the imperative client.hasPerk().

Run the app

Because the SDK contains native modules, rebuild the app after installation:

npx expo run:ios

Use npx expo run:android for Android. If you use EAS, create a new development build instead.

Next steps