Where this code goes, and what it assumes
Where this code goes, and what it assumes
Examples on this page are fragments. They render inside the provider you mounted
in the Quickstart:If something below does not work, one of these is usually missing. Each links
straight to the step that sets it up.
- Component registered —
app.use(creem)inconvex/convex.config.ts - Secrets set —
CREEM_API_KEYandCREEM_WEBHOOK_SECRETin Convex env - Billing API exported —
convex/billing.tscallingcreem.api({ resolve }) - Webhook registered —
creem.registerRoutes(http)inconvex/http.ts - Products created — one Creem product per plan and billing cycle
- Products synced —
npx convex run billing:syncBillingProducts - Styles imported —
@creem_io/convex/stylesafter the Tailwind import - Catalog defined —
billingCatalogmapping plan IDs to product IDs - Provider mounted —
<CreemConvexProvider>around your billing UI
Single owned product
A product purchased once. The card shows “Owned” afterwards and suppresses repeat checkout:Pricing page
Repeating products (consumables)
type="recurring" allows repeat purchases and never shows an “Owned” badge. Use it for credit packs and other consumables:
Pricing page
Credit grants
Credit fulfillment happens in the webhook, on the server. The purchase-to-credit mapping therefore has to live in a server-owned catalog, not in client code. Pass it to theCreem constructor in convex/billing.ts:
convex/billing.ts
checkout.completed webhook arrives for that product, the component grants the credits.
Refund behavior
creditGrant.refundBehavior answers one question. What happens to granted credits when the product is refunded?
Displaying and spending credits
Credits.Root renders a default balance card, or you can compose the slots yourself. Spending credits belongs in your own Convex action. The widget only displays and refreshes the balance:
convex/billing.ts
- React
- Svelte
src/CreditsPanel.tsx

credits.getBalance in your connected API:
Mutually exclusive products & upgrade paths
When one-time products supersede each other (Basic license → Premium license), declaretransition rules on the root. Once the user owns a lower tier, only valid upgrade paths render:
Pricing page

via_productchecks out a dedicated upgrade product, so the customer pays only the difference.directchecks out the target product at full price.
Handled in your frontend today. Transition rules are declared on
Product.Root and applied by
the widget. One effect worth knowing: a checkout started outside the widget sells the target
product at full price, since the grouping lives in your app rather than in Creem. Prefer this
handled by Creem directly? Vote for grouping non-subscription
products.What to keep in mind
type="one-time"is ownable,type="recurring"is repeatable. Choose per product. Both can live in the sameProduct.Root.- Subscription checkouts also create Creem orders, but the billing snapshot only exposes one-time orders as owned access. See Concepts.
- Full
Product.Root/Product.Itemprop tables live in the Reference.

