Skip to main content
Task guides for the parts you reach for once the basics work. For the ideas behind them, see Concepts.
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.

Custom auth and RBAC

Billing and identity are separate domains that meet in one place: the resolve callback. Your auth layer answers who is calling and what may they act on. This component answers what that billing entity owns and may change. resolve is the handover between the two. On the billing side, the generated API enforces its own boundary: it derives the entity from resolve, scopes customer data to that entity, and verifies explicitly selected subscriptions before reading or changing them. It never takes an entity from client input. On the identity side, sessions, roles, and org membership stay with whatever auth you already use. Convex Auth, Clerk, WorkOS, or your own tables all work the same way here, because the component only ever sees the result.

Quick start

creem.api({ resolve }) generates ready-to-export Convex functions. Each one calls your resolve callback to authenticate and determine the entityId. For team billing, verify membership and the required billing role before returning an organization ID, since that check belongs to the identity domain. This is what the Quickstart uses.

Full control

Call the resource namespaces (creem.subscriptions.*, creem.checkouts.*, creem.customers.*, creem.orders.*, creem.products.*) inside your own Convex functions and handle auth, entity resolution, and permission checks there. The library exports shared arg validators matching exactly what the widgets send, so your custom functions stay drop-in compatible: subscriptionUpdateArgs carries a kind discriminator for the three mutually exclusive update targets. The exported SubscriptionUpdateArgs type is a true discriminated union:
So "immediate" on a paid switch, or two targets at once, does not compile. Convex requires top-level args to be a flat object, which means the wire validator itself is permissive. The generated mutation calls parseSubscriptionUpdateArgs to re-check the same rules for calls arriving over the wire. Use it in your own wrappers too:
Admin-only billing, for example:
convex/billing.ts
Pair this with provider-level permissions so non-admins see disabled buttons instead of server errors. The server check above is what actually enforces the rule. Credit grants and spending are deliberately not part of ConnectedBillingApi. Expose an app-specific backend action for a business operation such as generateImage, and let that action call creem.credits.creditForEntity or creem.credits.debitForEntity with server-controlled amounts, references, and idempotency keys.

Resolver plan overrides

The resolver may return two optional overrides, for apps that own their plan assignment rather than letting the component own it: Omit both and the component reads the active plan from its own app-plan assignment rows.

Webhook event middleware

registerRoutes accepts an events map for app-specific logic. Your handlers run after the component’s built-in processing, which upserts customers, subscriptions, and orders. The ctx is a Convex action context. It has no ctx.db, so reach your own tables through ctx.runQuery and ctx.runMutation, and use ctx.runAction for third-party calls:
Dispatched events: checkout.completed, subscription.active, subscription.paid, subscription.canceled, subscription.scheduled_cancel, subscription.past_due, subscription.expired, subscription.trialing, subscription.paused, subscription.unpaid, subscription.update, refund.created, dispute.created. Dispute events are available to custom handlers only (no built-in sync).

Checkout gates and auto-resume

onBeforeCheckout fires before the widget calls checkouts.create, at either the provider or the widget level. Return false to abort. Use it for auth gates, terms acceptance, confirmation dialogs, or analytics:
Auto-resume after sign-in. If your callback saved the intent with pendingCheckout.save(intent), the widget notices when the Convex query re-fires with an authenticated user and re-triggers checkout itself. This works for modal auth such as Clerk or an Auth0 popup, and for redirect auth such as OAuth, with no manual resume code. Auto-resume is skipped when the user already has an active subscription or owns the product, so a sign-in cannot produce a duplicate purchase. Sibling guards for the other flows: onBeforePlanChange (paid switches and unit updates) and onBeforePlanActivation (app-owned plans like trials).

Internationalization

Set i18n once on the provider. Every connected widget inherits it, covering cards, dialogs, billing history, portal buttons, recovery banners, credits, and accessibility labels:
Product names and descriptions are merchant-owned content. Localize them in your catalog or with composition slots.

Custom billing UI model

uiModel returns everything the widgets need. To add app-specific fields, write your own query on top of creem.getBillingModel():
A null entityId is handled gracefully, so public pricing pages get the catalog without auth.

Creem server selection

The SDK defaults to the production API. Choose explicitly per deployment:
See Test Mode for the full test-environment workflow and test cards.

Webhook debug logging

The webhook handler logs the event type and event ID. It deliberately does not log the payload, which carries customer names and email addresses. When debugging an integration, opt in to the full body:
Unset it again once you are done - the verbose output puts customer PII in your deployment logs.
  • Why the entity model and API contract look the way they do: Concepts
  • Upgrading from 0.3.x, or retiring another provider: Migration
  • Direct SDK access and troubleshooting: Reference