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>
| Prop | Type | Default | Notes |
|---|---|---|---|
apiKey | string | required | Public key from the dashboard. Safe to ship in your bundle. |
customerEmail | string? | — | Required in web mode. In MV3 the SDK background owns identity. |
context | 'extension' | 'web'? | auto-detect | Override when SSR'ing or running in storybook. |
mode | 'live' | 'test'? | 'live' | Web-mode only. In extensions the SDK reads its own mode. |
apiBase | string? | https://api.crxpay.io | Override for self-hosted or staging. |
successUrl | string? | current URL | Where Stripe Checkout returns on success (web). |
cancelUrl | string? | current URL | Where Stripe Checkout returns on cancel (web). |
theme | Partial<BrandTheme>? | defaults | Brand overrides. See Theming. |
transport | Transport? | auto-create | Inject your own. Used by tests + the dashboard preview. |
UpgradeButton
<UpgradeButton priceId="price_pro_monthly">
Upgrade to Pro
</UpgradeButton>
| Prop | Type | Default | Notes |
|---|---|---|---|
priceId | string? | — | Stripe or crxpay price id. Omit to open the hosted paywall. |
children | ReactNode | 'Upgrade' | Label inside the button. |
variant | 'primary' | 'outline' | 'ghost' | 'primary' | Visual style. |
size | 'md' | 'sm' | 'md' | Button size. |
hideWhenPaid | boolean | true | Hide when subscription is active or trialing. |
className | string? | — | Extra class appended to .crxpay-button. |
style | CSSProperties? | — | Inline overrides. |
onCheckoutOpened | () => void | — | Fires after openCheckout resolves. Track conversion here. |
onError | (err) => void | — | Fires on failure. Errors also surface inline. |
disabled | boolean? | — | Disable explicitly. |
PricingTable
<PricingTable />
| Prop | Type | Default | Notes |
|---|---|---|---|
offering | Offering | null | useOfferings().current | Override the auto-fetched offering. |
ctaLabel | string | 'Choose plan' | Per-card CTA copy. |
hideWhenPaid | boolean | false | Hide cards once the customer is paying. |
className, style | — | — | Standard 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().
| Prop | Type | Default | Notes |
|---|---|---|---|
title | ReactNode | 'Choose your plan' | Headline. |
subtitle | ReactNode | 'Cancel anytime…' | Subhead. |
children | ReactNode? | <PricingTable /> | Replace the body entirely. |
portalContainer | HTMLElement? | document.body | Override portal target. |
keepOpenAfterPurchase | boolean | false | By 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`} />
| Prop | Type | Default | Notes |
|---|---|---|---|
warningThresholdDays | number | 3 | Switches to a danger style at or below this. |
format | (daysLeft) => ReactNode | 'X days left' | Replace the rendered copy. |
showWhenEnded | boolean | false | Render even at 0 days (otherwise hides). |
className, style | — | — | — |
Auto-hides when not trialing. Reads subscription.trialEnd from the
provider.
ManageSubscriptionLink
<ManageSubscriptionLink />
| Prop | Type | Default | Notes |
|---|---|---|---|
children | ReactNode | 'Manage subscription' | Anchor label. |
alwaysShow | boolean | false | By 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>
| Prop | Type | Default | Notes |
|---|---|---|---|
entitlement | string | required | Identifier from the dashboard's Entitlements page. |
children | ReactNode | required | Shown when entitled. |
fallback | ReactNode? | upgrade prompt | Shown when not entitled. Default opens <PaywallModal>. |
loading | ReactNode? | skeleton | Shown until the first subscription read resolves. |
ctaLabel | string | 'Upgrade to unlock' | Default-fallback CTA copy. |
message | ReactNode | '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/componentspage 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/sdkbackgroundcreateWebTransport({ 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.