crxpaydocs

Quickstart

Wire @crxpay/react-paywall into a Chrome extension popup in under five minutes.

This walks you through adding a paywall to a Chrome extension popup using the components. The same instructions apply to web apps — you'll just pass an extra customerEmail prop on the provider.

1. Install

pnpm add @crxpay/sdk @crxpay/react-paywall

You'll need both: @crxpay/sdk runs in your background service worker; the components run in the popup and proxy through it.

2. Configure the SDK in your background

import { CrxPay } from '@crxpay/sdk/background';

CrxPay.configure({
  apiKey: 'crxpay_pub_…', // from the dashboard's API keys page
  testMode: 'auto',       // detects unpacked extensions automatically
});

That's all the background needs. The SDK handles the signed cache, the FSM, and message routing.

3. Wrap your popup with the provider

import { createRoot } from 'react-dom/client';
import { CrxPayProvider } from '@crxpay/react-paywall';
import '@crxpay/react-paywall/styles.css';
import { App } from './App';

createRoot(document.getElementById('root')!).render(
  <CrxPayProvider apiKey="crxpay_pub_…">
    <App />
  </CrxPayProvider>,
);

The apiKey is the same crxpay_pub_* you use in the background. The provider auto-detects MV3 (chrome.runtime?.id is set) and proxies all calls through the background — no CORS, no extra plumbing.

4. Render a paywall

The simplest possible paywall:

import { PricingTable } from '@crxpay/react-paywall';

export function App() {
  return (
    <div className="popup">
      <h1>Upgrade to Pro</h1>
      <PricingTable />
    </div>
  );
}

<PricingTable> fetches the active offering from the dashboard, renders plan cards (highlighting the yearly one as "Best value"), and opens Stripe Checkout when clicked. No plumbing required.

5. Gate premium features

Wrap any UI you want to lock behind a paid plan:

import { EntitlementGate } from '@crxpay/react-paywall';

<EntitlementGate entitlement="pro">
  <ProOnlyFeature />
</EntitlementGate>

The default fallback shows a small "Upgrade to unlock" card that opens a <PaywallModal>. Pass your own fallback prop to customize.

Once a customer is paying, give them a way to manage their subscription:

import { ManageSubscriptionLink } from '@crxpay/react-paywall';

<footer>
  <ManageSubscriptionLink />
</footer>

Hides itself when there's no active subscription. Opens Stripe's hosted Billing Portal when clicked.

7. Apply your brand

Pass a theme to the provider — every component picks it up via CSS variables:

<CrxPayProvider
  apiKey="crxpay_pub_…"
  theme={{
    accent: '#10B981',  // emerald — your CTA + selected-plan accent
    text: '#0F2A21',    // optional override
    radius: '20px',     // optional — softer cards
  }}
>

</CrxPayProvider>

→ Full theme reference: Theming

8. Mount the paywall modal once near your root

The modal lives in a portal at document.body, so any component anywhere can trigger it via the usePaywall() hook:

import { CrxPayProvider, PaywallModal } from '@crxpay/react-paywall';

<CrxPayProvider apiKey="…">
  <App />
  <PaywallModal />
</CrxPayProvider>

Then in any descendant:

import { usePaywall } from '@crxpay/react-paywall';

function UpgradeBanner() {
  const { open } = usePaywall();
  return <button onClick={open}>Upgrade to unlock</button>;
}

You're done

The paywall is now live. When a customer completes Stripe Checkout, the SDK polls the API, updates its signed cache, and broadcasts a paid event to every component using your provider — including <EntitlementGate>, which re-renders to show the gated content.

→ See every prop in the API reference.

Was this page helpful?

Your feedback shapes what we document next.