React Native quickstart

Add Voidhash to an Expo or React Native app and gate your first premium feature.

This guide adds Voidhash to an existing Expo or React Native app. By the end you will have the SDK installed, an upgrade entry point, and an access check that gates a premium feature.

Install the SDK

Install the SDK package with the exact peer versions it supports.

npm install @voidhash/react-native react-native-nitro-modules@0.37 effect@4.0.0-rc.115

The SDK needs react-native-nitro-modules 0.37.x and effect 4.0.0-rc.115, so the command pins both. An unpinned effect resolves to an older major release that the SDK does not accept. Every Nitro-based module in the app must resolve to the same Nitro version, so check your other Nitro modules, such as react-native-iap, before you install. See Compatibility.

On iOS, the SDK links the VoidhashCore pod, which your Podfile has to reference.

In an Expo project, register the SDK's config plugin in app.json. It adds the pod when you prebuild.

app.json
{
  "expo": {
    "plugins": ["@voidhash/react-native"]
  }
}

In a bare React Native app, add the pod inside your app target in ios/Podfile, above use_native_modules!. The path is resolved through Node, so it works with any node_modules layout.

ios/Podfile
pod "VoidhashCore", :path => File.dirname(`node --print "require.resolve('@voidhash/ios/package.json', { paths: [require.resolve('@voidhash/react-native/package.json')] })"`.strip)

Then install the pods.

npx pod-install

Android needs no extra setup.

Connect your project

Link the app directory to a Voidhash project with the interactive initializer. The CLI is not a dependency of the SDK, so run it directly from your app's root.

npx voidhash-cli init

pnpm dlx voidhash-cli init works the same way. Install the CLI with --save-dev only if you want the Metro integration. That integration 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 three files:

  • voidhash.config.ts with your team and project slugs.
  • src/lib/voidhash.ts with the project's publishable key.
  • 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.

Give the SDK a URL scheme so purchase callbacks can return to your app. Expo projects set it in app.json.

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

In a bare React Native app, pass the same value when you create the client.

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

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

Expo Router treats incoming links as routes. If you use Expo Router, add a native intent handler so callback links do not become 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

Set up the smallest catalog that can grant access, plus the location your upgrade entry point asks for.

  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.

You do not need a store connection for this first run. The generated client enables development purchases in debug builds. Connect App Store or Google Play before you test a release build. See Development purchase mode for fixed prices, isolation, and lifecycle tools.

Regenerate the types whenever you change a product, perk, or location slug.

npx voidhash-cli types generate

For the model behind the catalog, see Products and perks and Paywalls.

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.

Add an upgrade entry point

Hosted paywalls are unavailable in the initial release

show() resolves to { status: "disabled" } for now, so every upgrade entry point needs a native screen of your own. Open it whenever show() does not report shown.

Resolve the paywall assigned to a location, call show(), and fall back to your own plans screen when nothing was shown.

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

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

export default function UpgradeScreen() {
  const router = useRouter();
  const { present, isPresenting } = voidhash.usePresentFlow("onboarding");

  return (
    <Button
      disabled={isPresenting}
      title="View plans"
      onPress={async () => {
        const result = await present();

        if (result.status === "failed") {
          // For example `FLOW_NOT_ASSIGNED` when the placement has no
          // published paywall yet.
          router.push("/plans");
        }
      }}
    />
  );
}

present() resolves when the paywall or flow ends, with how it ended: purchased, restored, finished, closed, or failed with a reason. Paywalls are drawn natively, which needs the renderer option of the config plugin. See Display a paywall.

Check access

Gate a feature on an active perk grant 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

The SDK contains native modules, so rebuild the app after installing it.

npx expo run:ios

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

Next steps