Skip to main content
The first argument is a plan_price_id (a specific billing variant - monthly vs. yearly), not a plan id.
useCheckout / usePortal and the prebuilt UI are redirect-only - they send the browser to a provider-hosted page. There is no prop that turns them into an embedded card form. For an embedded card you build the form with Stripe.js and drive it from the server’s headless methods (createSetupIntent -> confirm client-side -> createSubscriptionHeadless); those live on @kerne/server, not here.
openCheckout() redirects the browser directly (window.location.href = url). If you want the URL instead - to open in a new tab, or handle the redirect yourself - use createCheckout(), which returns the URL without navigating:
The billing portal works the same way, via usePortal():

Where the redirect goes

None of these hooks take a URL - checkout success/cancel and the portal return all resolve to the hosted portal, which then hands the session back to your app. See the server SDK’s Checkout docs for the exact resolution and how to configure app_url/portal_redirect_url.

The caller’s own subscription

The reactive hook covers most cases:
For an imperative, one-off read, use the underlying client directly:

Cancelling and switching plans

Both act on the payment provider first, then on Kerne — so a cancellation really stops the next invoice, rather than only closing off access on our side.
Neither hook refreshes useSubscription() for you — there is no shared cache to invalidate — so call refetch() once the action resolves if the same screen displays the subscription.
A plan change takes effect immediately, upgrade or downgrade alike, and the provider settles the difference by default. Pass { prorationBehavior: 'none' } to skip that and simply charge the new price from the next invoice. Deferring a downgrade to the end of the period is not supported yet.
cancelSubscription(id) ends the subscription at the end of the paid period. Pass true as the second argument to end it immediately instead. Either way the row survives with status canceled — cancelling is not deleting, and the billing history stays readable.

Building a plan picker

A page that lets a visitor pick any plan doesn’t know upfront whether they’re a new signup or already subscribed — and that distinction matters more than it looks. Starting a fresh checkout for someone already subscribed doesn’t replace their subscription: Stripe have no notion of “this session replaces the one on file”, so it creates a second, independent subscription while the first keeps billing. usePlanSwitch() makes that decision once, for every screen that needs it — it’s what <PricingTable> and the plan switcher inside <UserProfile> use internally:
No subscription yet (subscription is null) sends selectPlan straight to openCheckout() instead — there’s nothing to swap, so pending never gets set for that visitor. switchError, checkoutError and checkoutPriceId (busy state through the checkout redirect) are also on the return value, same idea as isCanceling/isChanging above.