Skip to main content
Plans, billing cycles, trials, unit-based pricing, and scheduled changes.
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.

Standard plans

Render catalog plans by ID. Titles, descriptions (Markdown), and prices resolve from synced Creem product data:
Pricing page
That renders:
  • Pricing cards, with a billing-cycle toggle that stays hidden when all plans share one cycle
  • A “Current plan” badge on the active plan
  • Plan switching, behind a confirmation dialog
  • A trial countdown badge
  • Cancel and resume, also behind confirmation
  • A scheduled-cancellation banner with an undo button
Four plan cards under a Monthly/Quarterly/Semi-annual/Yearly toggle: a Free plan, a Basic plan showing a trial countdown badge and Cancel subscription, a recommended Premium plan, and an Enterprise plan with Contact sales
You can also register plans inline, without a catalog. This helps when migrating incrementally:
Pricing page

Billing cycles

creemProductIds maps cycles to Creem products. Supported cycles: every-month, every-three-months, every-six-months, every-year. The interval selector appears automatically when plans expose more than one cycle:
A segmented control with Monthly selected, followed by Quarterly, Semi-annual, and Yearly
Add badges to promote longer cycles:
Pricing page

Trials

The component supports two trial paths:
  • Creem-managed trials are configured on paid Creem products. In the catalog they stay category: "paid" recurring plans; Creem owns checkout, card collection, and the subscription.trialing state. Declare the length with trialDays. The card then reads “Start free trial” instead of “Subscribe”, with the length as microcopy beneath it:
    src/billingCatalog.ts
    trialDays is display only. Creem still runs the trial, and once the subscription exists the card switches to a live countdown from trialEnd and stops reading the field.
    Creem’s product API doesn’t carry trial length yet, so the catalog is where the card learns it. One effect worth knowing: the length lives in two places, so update the catalog whenever you change the trial on the product. Prefer it to come from Creem instead? Two requests would get it there: trial information and duration in /v1/products, so the component syncs it alongside price and title, or Creem as Code, so the product and its trial are defined together in your repo.
  • App-owned no-card trials use category: "trial" with billingType: "custom". No Creem subscription is created. The component records activation history and the current app-plan assignment, which is what lets the billing UI show, activate, and hide the offer.
An app-owned trial that can only be used once per billing entity, and disappears once the entity picks a real plan in the same product line:
src/billingCatalog.ts
To let users activate the trial (or any app-owned plan), expose plans.activate in your connected API with an app mutation that calls:
convex/billing.ts
The component enforces the once-per-entity rule and hides ineligible trial cards. Your app still owns quota enforcement and lock states.
eligibilityScopeId and groupId are different axes: use groupId for pricing layout/audience tabs, and eligibilityScopeId for mutually exclusive entitlement alternatives. Scoped expiry only looks at active or scheduled non-trial plans in the same scope, so an add-on trial in an "analytics-addon" scope stays available after the customer picks a base plan.
Use onBeforePlanActivation (provider- or widget-level) for sign-in or consent gates before activation.

Unit-based pricing

Two workflows for quantity-based plans (seats are one kind of unit): User-selectable units. The customer picks a quantity before checkout:
Pricing page
Auto-derived units. Pass a fixed count from app state, such as an org member count:
Pricing page
When subscriptions.update is in the connected API, active unit-based plans show a “Change units” control.
For auto-derived units, keep the subscription in sync: when your member count changes, call subscriptions.update with the new units so billing reflects the current quantity.

Plan groups

Split plans by audience (e.g. Individual vs Teams). The root renders a group selector and derives billing cycles from the active group only:
Pricing page
An Individual/Teams group selector above a billing-cycle selector, with the Individual plans rendered beneath it
Control placement with groupSelector="auto" | "hidden" | "external" (use <Subscription.GroupSelector> for external composition), and group/onGroupChange for controlled state.

Scheduled changes: upgrades, downgrades, and paid-to-app-owned

updateBehavior controls paid-to-paid switches and unit changes:
  • "proration-charge-immediately" prorates and charges now. This is the default.
  • "proration-charge" prorates and puts the difference on the next invoice.
  • "proration-none" skips proration; the change applies next billing cycle.
  • "period-end" keeps the current subscription until currentPeriodEnd, then applies the change from a scheduled Convex job.
Pass a resolver to treat upgrades and downgrades differently:
Pricing page
Switches are confirmed in a dialog that spells out the effect of the behaviour you chose:
A Switch plan? dialog showing Basic at $50 per month changing to Free, a note that the trial continues until August 5 2026, and Confirm switch and Cancel buttons
Paid-to-app-owned is a cancellation flow rather than a Creem proration. This includes free plans, no-card trials, and custom entitlements fulfilled by your app. It has its own setting, appPlanUpdateBehavior:
  • "period-end" schedules Creem cancellation for the period boundary, then activates the app-owned target plan at that time. This is the default.
  • "immediate" cancels straight away and assigns the app-owned target plan now.
Export subscriptions.cancelScheduledUpdate in your connected API to give users an “Undo” for pending period-end changes; if the pending change was paid-to-app-owned, undoing also resumes the Creem scheduled cancellation.
Handled in Convex today. Free plans and period-end changes are tracked by this component, so both work without extra wiring. One effect worth knowing: the Creem portal only knows the current subscription, so surface pending changes yourself with ScheduledChangeBanner. Prefer this handled by Creem directly? Vote for free subscription tiers and scheduled subscription updates.

Custom card composition

When your design needs its own markup, keep the root and compose the pieces with unstyled. The root still owns checkout, switching, cancel, units, and active state:
src/PricingPage.tsx
Every slot resolves its content from the surrounding <Subscription.Item> context. Slots placed directly inside <Subscription.Grid> have no item to read from, so they throw at render time. Wrap each card in a <Subscription.Item planId="…">.
In styled mode, the library’s defaults live in the base cascade layer, so your class/className utilities override them without tailwind-merge. <Subscription.Grid> reads the root’s columns prop, so a composed layout and the default cards resolve their column count the same way.

Typed bindings

For production apps, createCreemReact and createCreemSvelte bundle catalog, API, and defaults into one typed object. Plan IDs then autocomplete and stay checked:
src/billingCatalog.ts

What to keep in mind

  • subscriptions.update, cancel, resume, and cancelScheduledUpdate in the connected API each unlock their controls; omit what you don’t want users doing.
  • UI permissions are cosmetic. Server functions must still check real access. See Advanced → Custom auth and RBAC.
  • The full prop reference for Subscription.Root, Subscription.Item, and every slot component lives in the Reference.