Upgrading from 0.3.x
v2 (0.4.0) is a breaking release. The tables below map every removed or changed API to its replacement.creem is now a peer dependency
creem moved out of this package’s dependencies, so it is no longer installed
for you. Add it to your app or the package will fail to resolve at runtime:
.svelte source and reassign $derived state, which earlier 5.x releases
reject at compile time.
If you share a billing catalog between server and browser code, import the
helpers from @creem_io/convex/core rather than the root entry — the root is
the server client and pulls the Creem Node SDK into your browser bundle:
Removed primitives
These exports were deleted. Importing them is a build failure.BillingToggle was a thin wrapper that rendered cycle labels into a
SegmentGroup. Inside a Subscription.Root, Subscription.IntervalSelector
replaces it and is already wired to the root’s cycles; outside one, compose
SegmentGroup directly.
Typed connected API and connectCreemApi
ConnectedBillingApi fields are now typed by function kind, args, and
return type, so wiring the wrong generated export fails to compile. Build the
object with connectCreemApi(api.billing) instead of by hand.
Resolver returns null for anonymous callers
ApiResolver now returns ResolvedBillingIdentity | null. Returning null is
the anonymous signal; a thrown error is a real failure that is logged and
rethrown instead of silently degrading a signed-in user to the logged-out view.
subscriptions.update is a discriminated union
Those combinations no longer typecheck, and
parseSubscriptionUpdateArgs
re-checks them for calls arriving over the wire. A plan switch no longer accepts
units: Creem applies a product upgrade and a quantity change as separate
operations, so send a second kind: "units" update instead. Unit counts are
validated server-side (integer, 1 – 1,000,000).
Widget namespaces are plain objects
<Subscription>, <Product>, and <Credits> are no longer callable
components. Subscription and Product used to alias their Item, while
Credits aliased its Root. Rendering the bare namespace silently produced
the wrong element.
Binding and provider props
createCreemReact and createCreemSvelte no longer return config, which the
provider never accepted. The binding is spreadable:
PaymentRecoveryButton now reads customers.portalUrl from the provider;
portalUrl is an optional override rather than a required prop.
Model and return shapes
subscriptions.getCurrent no longer throws Product not found when a
subscription references a product this deployment has not synced yet - that
failure took down every query composed on top of it. product is now nullable,
so narrow it before use:
Subscription lifecycle behaviour
These are bug fixes rather than API changes, but they change what your app sees at runtime, so review any entitlement code you wrote against 0.3.x.Provider-based widget API
Connected widgets no longer accept directapi={billingApi} props. Wrap the
area that renders billing UI with CreemConvexProvider and pass the API,
catalog, permissions, and consent hooks there.
This is intentionally breaking so apps have one billing integration boundary
instead of repeating Convex function references throughout the UI.
Unit-based billing rename
The UI widget API now follows Creem’s unit-based pricing language. There are no backward-compatible aliases for the previous seat-specific names.
The
units prop remains the quantity passed to checkout and subscription
updates. A unit can still represent a seat, but the public API no longer assumes
that seats are the only unit-based pricing use case.
Pricing columns
twoColumnLayout was removed. Use columns={2} for a fixed two-column pricing
grid, or omit it for automatic layout.
Product namespace cleanup
Product.Group was removed. It was only a compatibility alias for
Product.Root, not a real grouping primitive. Render Product.Root directly.
Subscription.Group is no longer an alias for Subscription.Root
PricingSection props
PricingSection is a public primitive, and its props changed beyond the
seat→unit rename. (PricingCard already took activePlanId in 0.3.x and is
unaffected.)
AvailableAction covers billing state only
switch_interval, update_units, and contact_sales were removed from
AvailableAction and from snapshot.availableBillingActions. They described
the shape of a plan, not the entity’s billing state, and the v2 resolver never
emitted them — so a BillingGate requiring one already rendered only its
fallback. Removing them turns that silent no-op into a compile error.
Answer those questions from the catalog entry instead:
checkout, portal, cancel, and reactivate are unchanged.
cycleBadges merges instead of replacing
<Subscription.IntervalSelector cycleBadges={...} /> in React used to replace
the root’s badge map wholesale, so setting one interval’s badge silently dropped
the rest. It now merges per interval, falling back to the root’s badge for any
interval the prop omits — matching Svelte, which already behaved this way.
To keep the old “replace everything” behaviour, pass a badge for every interval
you render.
orders.list returns unpaid orders
orders.list previously filtered to status === "paid". It now returns every
one-time order, including pending, refunded, and partially_refunded ones,
so the caller decides what counts as owned. snapshot.access and
ownedProductIds still apply the paid filter for you.
If you treated everything orders.list returned as granting access, filter on
status explicitly:
App-owned plans and i18n
freePlans.activate and onBeforeFreePlanActivation were removed:
Use the new plan-based API for free, trial, or other app-owned catalog plans.
Convex-Creem writes
snapshot.appPlanAssignments for the current app-owned
plan; host apps only need a custom activePlanId projection when they
intentionally want to override the component-managed assignment. Price interval
suffixes now come from labels.priceInterval, so override those labels instead
of formatting /mo or /yr in application code.
App-plan eligibility helpers
isAppPlanEligible and shouldShowPlan now take an eligibility context object
as their third argument. Pass activePlanId, activeOrScheduledPlanIds, and
catalogPlans explicitly so once-per-entity and scoped trial rules evaluate
against the same billing state as the widgets.
For scoped trials, include
activeOrScheduledPlanIds and catalogPlans:
Billing Snapshot Contract
creem.getBillingSnapshot(...) and the generated
creem.api({ resolve }).snapshot query return the canonical BillingSnapshot
shape with explicit arrays for subscriptions, one-time orders, and app-owned
plan assignments, plus a derived access projection.
The generated
uiModel used by connected widgets exposes the same canonical
snapshot as uiModel.snapshot.
Product credit grants
Product.Item no longer accepts checkout metadata for credit grants. Put
purchase-to-credit mappings in the server-owned billing catalog with
creditGrant so webhook fulfillment derives the amount from trusted app code.
By default, catalog-granted credits are reversed only on full refunds. Configure
refundBehavior per product when your app needs proportional ("prorate"),
eager ("debit"), or manual ("none") refund handling.
Retiring a previous billing provider
Moving from another billing integration (Stripe, LemonSqueezy, a custom setup)? The component’s setup is additive, but removing the old system safely needs a manual inventory:- Old webhook endpoints, both in your app and in the old provider’s dashboard (
/api/stripe/webhook,/webhooks/lemonsqueezy, and so on). Keep them only while another live flow depends on them. - Client env vars: old product, price, plan, and publishable-key variables in the
VITE_*,NEXT_PUBLIC_*, andPUBLIC_*namespaces. - Server env vars: old API keys, webhook secrets, portal config, and price IDs. These hide in several places at once, including local
.env*files, hosting provider settings, CI secrets, and the old dashboard. - Background jobs: scheduled syncs or functions still reading old billing env vars or processing old webhook events.