Skip to main content
Gating features on billing state, evaluating usage limits, and the account-level widgets. The state model itself is explained in 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.

Gating UI with BillingGate

BillingGate conditionally renders based on which billing actions are available in the snapshot (checkout, portal, cancel, reactivate):
Account page

Usage limits

Catalog plans can carry limits (arbitrary numeric keys like projects or aiMessages). evaluateUsageLimits compares your app’s usage counters against the active plan’s limits:
The library evaluates the limits. Your app owns the actual usage counters and decides where they live.
UI gates are user experience, not security. Protected backend actions must check billing state server-side. Read the snapshot inside your Convex function before doing the work.

UI permissions

BillingPermissions controls which widget buttons are enabled. Use it for role-based UI, such as letting only org admins manage billing. Set it once at the provider:
Account page
Disabled permissions render greyed-out buttons. This is cosmetic gating only. Enforce the real rules in your Convex functions, as described in Advanced → Custom auth and RBAC.

Account widgets

Billing portal

Opens the Creem customer portal, which is where payment methods, billing details, and invoice documents live. It hides itself when the entity has no Creem customer record yet, since customers are created on first checkout:
Account page
Requires customers.portalUrl in the connected API.

Billing email

The Creem customer portal does not let a customer change the email address invoices and receipts go to. <BillingEmail> does: it shows the address Creem currently uses, and saves a new one through Creem’s customer API.
Account page
Like the portal button, it renders nothing until the entity has a Creem customer, and nothing when canManageBillingEmail is false. It reloads when the billing entity changes. Each request also carries the entity the form shows, and the generated actions refuse it when your resolver now returns a different one, so switching organizations in another tab never saves one organization’s address into another. A failed load offers a retry button. Requires customers.billingEmail and customers.updateBillingEmail in the connected API. Export customersBillingEmail and customersUpdateBillingEmail from convex/billing.ts and connectCreemApi wires them:
convex/billing.ts
The generated actions let any caller your resolve accepts change the entity’s billing email. If only billing admins may do that, write your own action with customersUpdateBillingEmailArgs, keep the expectedEntityId check, and check the role before calling creem.customers.updateBillingEmail, as in Advanced → Custom auth and RBAC. Creem rejects an address that another customer of your store already uses. The widget then shows its generic save error. When the same person checked out for several of your billing entities, those entities share one Creem customer; the widget then refuses the change and says the address is shared, because it is also the other entities’ billing address. <BillingEmail /> is the ready-made layout. For your own markup, compose the parts inside <BillingEmail.Root>; the root keeps the state and wires the label, description, and error to the input:
Account page
Svelte uses the same names: <BillingEmail.Root>, <BillingEmail.Input />, and so on. See the reference for every part.

Billing history

Paginated transaction history, backed by Creem’s transaction search. This renders transaction rows, not invoice documents:
Account page
A billing history table with Date, Description, Type, Status, and Amount columns listing five paid transactions, above a pager running from page 1 to page 22A billing history table with Date, Description, Type, Status, and Amount columns listing five paid transactions, above a pager running from page 1 to page 22
Requires transactions.search in the connected API. Export transactionsSearch from convex/billing.ts and connectCreemApi wires it automatically.
Portal-only today. Payment methods, billing details, and invoice documents are handled by Creem’s hosted portal, which <BillingPortal> opens for the customer. Prefer them embedded in your own UI as Widgets? Vote for payment method, billing preference, and invoice APIs.

Payment recovery

When a subscription is past due or a payment failed, surface it and route the user to the portal to fix their payment method:
Account page
A yellow past-due banner above a red payment-failed banner, followed by an Update payment method buttonA yellow past-due banner above a red payment-failed banner, followed by an Update payment method button
The banner derives its state from snapshot.paymentRecoveryState (or accepts an explicit state). The button reads customers.portalUrl from the provider like every other connected widget, and renders nothing when no portal action is wired.

Checkout success

Show a confirmation banner when the user returns from checkout. Creem’s query parameters are parsed for you:
Account page
React additionally exports a useCheckoutSuccessParams() hook returning the parsed params.

Status banners

Presentational components for specific states, fed from the snapshot:

Server-side entitlement checks

The same snapshot powers backend enforcement:
convex/projects.ts
Widget gates keep the UI honest. This check is what actually protects the feature.

Checking a single subscription

When you gate on subscriptions.getCurrent rather than the snapshot, test the status - never the mere existence of a subscription. A returned subscription may be unpaid or paused, in which case it must not grant access:
isActiveSubscriptionStatus treats active, trialing, scheduled_cancel, and past_due as entitling. past_due is a payment-retry window rather than a loss of access - surface it with PaymentRecoveryBanner instead of cutting the user off.