Hooks

React hooks for identity, placements, events, and reactive SDK state.

Beta

The Web SDK is in beta and its API may change between releases.

All hooks must be used under a SuperwallProvider.

useUser

Identity, attributes, and subscription state in one object. It re-renders when any of them change.

import { useUser } from "@superwall/paywalls-react";

function Account() {
  const {
    id,
    aliasId,
    effectiveId,
    isLoggedIn,
    attributes,
    subscriptionStatus,
    entitlements,
    identify,
    signOut,
    setAttributes,
    // also available: integrationAttributes, customerInfo,
    // setIntegrationAttribute, setIntegrationAttributes
  } = useUser();

  return (
    <>
      <p>{isLoggedIn ? id : "anonymous"}</p>
      <button onClick={() => identify("user_42")}>Sign in</button>
      <button onClick={() => signOut()}>Sign out</button>
    </>
  );
}

entitlements is derived from subscriptionStatus and contains the active ones.

usePlacement

Returns a register function plus the state of the latest placement from this hook's calls. The state is local to the component.

import { usePlacement } from "@superwall/paywalls-react";

function UpgradeButton() {
  const { register, state } = usePlacement({
    onPresent: (info) => console.log("opened", info.identifier),
    onDismiss: (_info, result) => console.log("dismissed", result),
    onSkip: (reason) => console.log("skipped", reason),
    onError: (error) => console.error(error),
  });

  return (
    <>
      <button onClick={() => register({ placement: "checkout" })}>Upgrade</button>
      <p>paywall: {state.type}</p>
    </>
  );
}

The handler you pass fires alongside the global delegate rather than replacing it.

The state is per-hook, so two components never see each other's outcomes. The SDK still allows only one paywall on screen at a time. If another component already has one up, register resolves as { type: "error" } with a PaywallAlreadyPresentedError.

useSuperwall

The instance itself, for anything the other hooks do not cover.

const sw = useSuperwall();
const products = await sw.purchases.getProducts();

useSignal

Subscribes to any Readable<T> the SDK exposes and re-renders on change.

import { useSignal, useSuperwall } from "@superwall/paywalls-react";

function PaywallBadge() {
  const sw = useSuperwall();
  const isPresented = useSignal(sw.isPaywallPresented);
  return isPresented ? <span>Paywall open</span> : null;
}

useSuperwallEvent

Subscribes to a typed SDK event for the lifetime of the component.

useSuperwallEvent("subscriptionStatus_didChange", () => {
  refetchAccount();
});

Cleanup is handled on unmount. See Events for what is emitted.

useDelegate

Registers a global delegate from inside React.

useDelegate({
  onSubscriptionStatusChange: (from, to) => {
    console.log("subscription", from.status, "->", to.status);
  },
});

Only one delegate is active at a time. useDelegate overrides a delegate passed to SuperwallProvider, and when the last useDelegate unmounts it clears the delegate entirely, including the provider's. Use one or the other, not both.

Custom placements defined in the paywall editor arrive as an event rather than through the delegate:

useSuperwallEvent("custom_placement", (e) => {
  if (e.detail.placementName === "contact_support") openIntercom();
});

useCustomPaywall

Renders your own paywall UI instead of the default iframe. The SDK still runs the full trigger pipeline and fires the same lifecycle events.

The hook does not present anything on its own. Call register() yourself. paywall is null until a paywall presents, and carries the state snapshot and the controller.

const { register, paywall } = useCustomPaywall({ placement: "checkout" });

return (
  <>
    <button onClick={() => void register()}>Upgrade</button>
    {paywall && (
      <MyPaywall
        products={paywall.state.products}
        busy={paywall.state.transaction.phase === "purchasing"}
        onBuy={(product) => void paywall.controller.buy(product)}
        onRestore={() => void paywall.controller.restore()}
        onClose={() => paywall.controller.close()}
      />
    )}
  </>
);

controller.buy takes the Product the user chose. paywall.state also carries restoration and paywallInfo.

SuperwallPaywall

A declarative gate that renders the SDK's default paywall, not your own UI. It calls register on mount, shows loading until the paywall presents, and renders children once the feature unlocks.

<SuperwallPaywall placement="checkout" loading={<Spinner />}>
  <ProFeature />
</SuperwallPaywall>

Pass inline to mount the paywall iframe in place instead of as a full-viewport overlay.

Custom paywall rendering is the least stable part of the beta API. Check the example apps before building on it.

How is this guide?

On this page