Skip to main content
One-time products, consumables, and credit packs.
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.

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 the Creem constructor in convex/billing.ts:
convex/billing.ts
Set the trusted product ID in Convex env (never read it from a browser-exposed variable for fulfillment):
When a 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
src/CreditsPanel.tsx
A Credit Balance card showing 490 credits with a refresh icon and a Generate image button costing 10 credits
The widget needs only credits.getBalance in your connected API:
Keep the spend amount, reference, and idempotency policy in the app-owned backend action. UI code can be manipulated; the action is the enforcement point.

Mutually exclusive products & upgrade paths

When one-time products supersede each other (Basic license → Premium license), declare transition rules on the root. Once the user owns a lower tier, only valid upgrade paths render:
Pricing page
Two product cards side by side: Basic at $400 marked Owned with no buy button, and Premium at $700 offering an Upgrade button
Two transition kinds:
  • via_product checks out a dedicated upgrade product, so the customer pays only the difference.
  • direct checks 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 same Product.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.Item prop tables live in the Reference.