Skip to Content
SDK v3newReact / Next.jsOverview

React bindings — @monetize.software/sdk-react

Provider, hooks and declarative components on top of @monetize.software/sdk. ≤ 2 KB gzip (bindings only — the UI lives inside the SDK).

Works in both browser scenarios:

  • Drop-in PaywallUI — use <PaywallButton> / <PaywallGate> to open the modal declaratively, plus hooks (usePaywallUser, usePaywallTrial) to read state in your components.
  • Custom UI — skip <PaywallButton> / <PaywallGate>, use hooks only (usePaywallPrices, usePaywallAccess, usePaywallUser) to power your own pricing cards, feature gates, and dashboards.
pnpm add @monetize.software/sdk-react @monetize.software/sdk react

Requires react: >= 18 (uses useSyncExternalStore for concurrent-safe snapshots).

Working reference app: github.com/monetize-software/sdk-examples/tree/main/nextjs  — Next.js 16 + App Router + Tailwind. Every hook and component shown below is wired into a runnable page; clone, set two env vars, npm install, npm run dev.

Quick start

import { PaywallProvider, PaywallGate, PaywallButton, usePaywallUser } from '@monetize.software/sdk-react'; function App() { return ( <PaywallProvider options={{ paywallId: 'YOUR_ID', apiOrigin: 'https://pay.your-domain.com', auth: true }} > <PaywallGate fallback={<UpgradeCTA />}> <PremiumFeature /> </PaywallGate> <PaywallButton>Upgrade</PaywallButton> </PaywallProvider> ); } function UpgradeCTA() { const account = usePaywallUser(); if (account.status === 'loading') return <p>…</p>; if (account.status === 'guest') return <p>Hi guest! Unlock full access.</p>; return <p>Hi, {account.user?.email ?? 'there'}! Unlock full access.</p>; }

apiOrigin must match the custom_domain configured for your paywall on the platform.

<PaywallProvider>

Accepts one of two mutually exclusive props (TypeScript discriminated union):

<PaywallProvider options={{ paywallId: '3', apiOrigin: 'https://pay.your-domain.com', auth: true }} > <App /> </PaywallProvider>

The instance is built inside useEffect (client-only); on the server the context value is null. Every hook does a graceful fallback (null / { status: 'loading' }), so the Provider is safe to render in Next.js without 'use client' shenanigans on the subtree.

The cleanup effect calls paywall.destroy() — StrictMode double-mount won’t leak.

options is not reactive. The Provider creates the instance once; switching paywallId between renders won’t rebuild PaywallUI. Force it via key:

<PaywallProvider key={paywallId} options={{ paywallId, apiOrigin }}>

Reactive option swaps are intentionally not supported — use key to force a fresh instance.

What’s available

SSR / Next.js App Router

@monetize.software/sdk-react ships a 'use client' directive on its entrypoint — the bundler crosses the client boundary itself, so the host can import PaywallProvider / hooks directly in a server component without wrapping.

// app/providers.tsx 'use client'; import { PaywallProvider } from '@monetize.software/sdk-react'; export function PaywallProviders({ children }) { return ( <PaywallProvider options={{ paywallId: process.env.NEXT_PUBLIC_PAYWALL_ID!, apiOrigin: process.env.NEXT_PUBLIC_PAYWALL_ORIGIN!, auth: true }} > {children} </PaywallProvider> ); } // app/layout.tsx import { PaywallProviders } from './providers'; export default function RootLayout({ children }) { return ( <html> <body> <PaywallProviders>{children}</PaywallProviders> </body> </html> ); }

Hooks called from server components return the SSR snapshot (null / loading). On the client, real data flows in after mount.

Which SDK channel does it work with?

@monetize.software/sdk-react is drop-in for any structurally compatible PaywallUI:

ScenarioSource of PaywallUI
Web / SPAProvider options → internally new PaywallUI() from @monetize.software/sdk.
Chrome Extension (React popup)createPaywallUI() from @monetize.software/sdk-extension (Remote-proxied) → Provider instance.
Tests (vitest + RTL)Mocks or a real PaywallUI → Provider instance.

Re-exported types

For ergonomics, core SDK types are re-exported — the host doesn’t need a second import from @monetize.software/sdk:

import type { PaywallUI, PaywallUIOptions, PaywallEvent, PaywallEventHandler, PaywallStateSnapshot, PaywallAccessResult, GetAccessOptions, OpenOptions, AnalyticsOptions, PaywallUser, PaywallPrice, PaywallBootstrap, PaywallSettings, PaywallOffer, Identity, AuthSession } from '@monetize.software/sdk-react';

PaywallUserState (the discriminated union returned by usePaywallUser()) is exported directly from @monetize.software/sdk-react:

import type { PaywallUserState } from '@monetize.software/sdk-react';

Single source of truth is still @monetize.software/sdk — this is pure pass-through.

Next steps