crxpaydocs

API reference

Every prop, every hook, every type that ships in @crxpay/react-paywall.

Complete prop + hook reference. For walk-throughs, see the Quickstart. For brand customization, see Theming.


CrxPayProvider

The root context provider. Wrap your tree once, near the root.

<CrxPayProvider
  apiKey="crxpay_pub_…"
  customerEmail="user@example.com"
  theme={{ accent: '#10B981' }}
>
  <App />
</CrxPayProvider>
PropTypeDefaultNotes
apiKeystringrequiredPublic key from the dashboard. Safe to ship in your bundle.
customerEmailstring?Required in web mode. In MV3 the SDK background owns identity.
context'extension' | 'web'?auto-detectOverride when SSR'ing or running in storybook.
mode'live' | 'test'?'live'Web-mode only. In extensions the SDK reads its own mode.
apiBasestring?https://api.crxpay.ioOverride for self-hosted or staging.
successUrlstring?current URLWhere Stripe Checkout returns on success (web).
cancelUrlstring?current URLWhere Stripe Checkout returns on cancel (web).
themePartial<BrandTheme>?defaultsBrand overrides. See Theming.
transportTransport?auto-createInject your own. Used by tests + the dashboard preview.

UpgradeButton

<UpgradeButton priceId="price_pro_monthly">
  Upgrade to Pro
</UpgradeButton>
PropTypeDefaultNotes
priceIdstring?Stripe or crxpay price id. Omit to open the hosted paywall.
childrenReactNode'Upgrade'Label inside the button.
variant'primary' | 'outline' | 'ghost''primary'Visual style.
size'md' | 'sm''md'Button size.
hideWhenPaidbooleantrueHide when subscription is active or trialing.
classNamestring?Extra class appended to .crxpay-button.
styleCSSProperties?Inline overrides.
onCheckoutOpened() => voidFires after openCheckout resolves. Track conversion here.
onError(err) => voidFires on failure. Errors also surface inline.
disabledboolean?Disable explicitly.

PricingTable

<PricingTable />
PropTypeDefaultNotes
offeringOffering | nulluseOfferings().currentOverride the auto-fetched offering.
ctaLabelstring'Choose plan'Per-card CTA copy.
hideWhenPaidbooleanfalseHide cards once the customer is paying.
className, styleStandard styling escape hatches.

The table picks one package as "recommended" (yearly preferred, then lifetime). When both monthly and yearly variants exist, it computes "Save X%" automatically.

PaywallModal

<PaywallModal />

// Trigger from anywhere:
const { open } = usePaywall();
<button onClick={open}>Upgrade</button>

Mount once near your root. Opens via usePaywall().open().

PropTypeDefaultNotes
titleReactNode'Choose your plan'Headline.
subtitleReactNode'Cancel anytime…'Subhead.
childrenReactNode?<PricingTable />Replace the body entirely.
portalContainerHTMLElement?document.bodyOverride portal target.
keepOpenAfterPurchasebooleanfalseBy default the modal auto-closes when subscription transitions to paid.
onClose() => void?Fires on Esc / backdrop / X / auto-close.

Behaviour: focus trap inside the dialog, body scroll lock while open, restores focus to the trigger element when closed. Honours prefers-reduced-motion.

TrialBadge

<TrialBadge />
<TrialBadge format={(d) => `Trial ends in ${d}d`} />
PropTypeDefaultNotes
warningThresholdDaysnumber3Switches to a danger style at or below this.
format(daysLeft) => ReactNode'X days left'Replace the rendered copy.
showWhenEndedbooleanfalseRender even at 0 days (otherwise hides).
className, style

Auto-hides when not trialing. Reads subscription.trialEnd from the provider.

<ManageSubscriptionLink />
PropTypeDefaultNotes
childrenReactNode'Manage subscription'Anchor label.
alwaysShowbooleanfalseBy default hides when subscription isn't active or trialing.
className, style
onError(err) => void?Fires when openBillingPortal throws.

Opens the Stripe Billing Portal in the same tab.

EntitlementGate

<EntitlementGate entitlement="pro">
  <ProOnlyFeature />
</EntitlementGate>
PropTypeDefaultNotes
entitlementstringrequiredIdentifier from the dashboard's Entitlements page.
childrenReactNoderequiredShown when entitled.
fallbackReactNode?upgrade promptShown when not entitled. Default opens <PaywallModal>.
loadingReactNode?skeletonShown until the first subscription read resolves.
ctaLabelstring'Upgrade to unlock'Default-fallback CTA copy.
messageReactNode'This feature is part of a paid plan.'Default-fallback message.

Hooks

All hooks throw if called outside <CrxPayProvider>. Wrap your tree first.

useCrxPay()

Raw context accessor.

interface CrxPayContextValue {
  transport: Transport;
  context: 'extension' | 'web';
  subscription: Subscription | null;
  loading: boolean;
  error: Error | null;
  refresh: () => Promise<void>;
  paywallOpen: boolean;
  openPaywall: () => void;
  closePaywall: () => void;
  theme: BrandTheme;
}

useSubscription()

const { subscription, isActive, isTrialing, refresh } = useSubscription();
{
  subscription: Subscription | null,
  loading: boolean,
  error: Error | null,
  isActive: boolean,
  isTrialing: boolean,
  refresh: () => Promise<void>,
}

refresh forces a network round-trip, bypassing the SDK's signed cache. Call after a manual entitlement grant or a checkout completion outside the SDK's flow.

useEntitlement(identifier)

const { granted } = useEntitlement('pro');
if (granted) renderPro();
{ granted: boolean, loading: boolean }

Returns false while loading. For most cases you don't care — only show a skeleton if the absence flashes annoyingly.

useOfferings()

const { current, loading } = useOfferings();
{
  offerings: Offerings | null,
  current: Offering | null,
  loading: boolean,
  error: Error | null,
  refresh: () => Promise<void>,
}

Caches for the lifetime of the provider — offerings don't change often, and the SDK's KV cache covers it server-side too.

usePaywall()

const { isOpen, open, close } = usePaywall();
{ isOpen: boolean, open: () => void, close: () => void }

Programmatic control of the <PaywallModal> mount. Multiple components can trigger it without prop-drilling — the open state lives in the provider.


Types

Re-exported from @crxpay/types:

import type {
  Subscription,
  Offerings,
  Offering,
  OfferingPackage,
  Entitlement,
} from '@crxpay/types';

BrandTheme

See Theming for the full interface and override paths.

Transport

interface Transport {
  getSubscription(): Promise<Subscription>;
  syncSubscription(): Promise<Subscription>;
  getOfferings(): Promise<Offerings>;
  openCheckout(priceId?: string): Promise<void>;
  openBillingPortal(): Promise<void>;
  identify(opts: { email?: string; externalUserId?: string }): Promise<void>;
  onSubscriptionChange(fn: (sub: Subscription) => void): () => void;
  onPaid(fn: (sub: Subscription) => void): () => void;
}

Inject a custom transport via the provider's transport prop. Useful for:

  • Tests that need deterministic subscription state
  • Storybook / Ladle previews
  • Dashboard playgrounds (the dashboard's /paywalls/components page uses this to stub Stripe so component-clicks don't fire real Checkout sessions)

The package ships two ready-made transports:

  • createExtensionTransport() — proxies through @crxpay/sdk background
  • createWebTransport({ apiKey, customerEmail, mode, apiBase }) — direct API

The provider auto-selects the right one when neither transport nor context is supplied.


Paywall config schema

Not part of v0.1. Phase 8 ships <Paywall config={…}/> — a single renderer that consumes a JSON config produced by templates / the visual editor / (later) AI.

Skim ahead at the Paywall config types in the repo if you're curious.

Was this page helpful?

Your feedback shapes what we document next.