Billing
Add pricing, subscriptions, checkout, seats, usage, and billing management without coupling the UI to one provider.
The billing plugin adds a complete billing tab to personal or organization settings. Every component reads a provider-neutral BillingAdapter. BAUI includes adapters for Stripe, Polar, Autumn, Creem, Dodo Payments, and Commet.
The UI includes:
- Pricing plans with monthly, yearly, and one-time prices
- Current subscription status
- Checkout and plan changes
- Billing portal access
- Cancellation and restoration
- Seat management
- Metered usage
- Personal and organization billing
Setup
Configure a Better Auth billing plugin
Configure one of Better Auth's supported billing plugins on the server and client. Apply its database schema before you open the billing page.
See the provider guides for Stripe, Polar, Autumn, Creem, Dodo Payments, and Commet.
Install the registry item
bunx shadcn@latest add @better-auth-ui/billingThis installs the billing views, the local plugin factory, and the required shadcn/ui components.
Create an adapter
Define the plans once in BAUI's generic format. Amounts use the currency's smallest unit. For example, 2000 USD means $20.00.
import {
type BillingPlan,
createStripeBillingAdapter
} from "@better-auth-ui/core/plugins/billing"
import { authClient } from "@/lib/auth-client"
const plans = [
{
id: "pro",
name: "Pro",
description: "For teams shipping production applications.",
prices: [
{ id: "pro-month", amount: 2000, currency: "USD", interval: "month" },
{ id: "pro-year", amount: 19200, currency: "USD", interval: "year" }
],
features: ["Unlimited projects", "Priority support"],
highlighted: true,
seatBased: true
}
] satisfies BillingPlan[]
export const billingAdapter = createStripeBillingAdapter(authClient, {
plans,
successUrl: "/settings/billing?checkout=success",
cancelUrl: "/settings/billing?checkout=canceled",
returnUrl: "/settings/billing"
})Register the plugin
This snippet shows only the billing additions. The TanStack Start and Next.js guides cover the full provider, including where authClient and navigate come from.
import { AuthProvider } from "@/components/auth/auth-provider"
import { billingPlugin } from "@/lib/auth/billing-plugin"
import { organizationPlugin } from "@/lib/auth/organization-plugin"
import { billingAdapter } from "@/lib/billing"
<AuthProvider
authClient={authClient}
navigate={navigate}
plugins={[
organizationPlugin(),
billingPlugin({
adapter: billingAdapter,
user: true,
organization: true
})
]}
>
{children}
</AuthProvider>Organization requests always include the organization ID and slug. Your custom adapter must authorize the requested organization. Do not infer it from active organization state.
Polar adapter
Map each BAUI plan ID to a Polar product ID or checkout slug. The adapter uses the mapping in both directions, so the UI can match a Polar subscription to its BAUI plan.
import { createPolarBillingAdapter } from "@better-auth-ui/core/plugins/billing"
export const billingAdapter = createPolarBillingAdapter(authClient, {
plans,
products: {
pro: { type: "product", value: "123e4567-e89b-12d3-a456-426614174000" }
},
successUrl: "/settings/billing?checkout=success",
cancelUrl: "/settings/billing?checkout=canceled",
returnUrl: "/settings/billing"
})Polar handles cancellation, restoration, and seat changes in its customer portal. Its adapter marks those direct actions as unsupported, so BAUI shows a manage-billing action instead. Stripe performs the actions through Better Auth's subscription endpoints.
Other bundled adapters
Each adapter reflects the official client API. It does not claim support for actions that the provider handles in its portal.
| Adapter | Checkout and state | Direct actions | Scope |
|---|---|---|---|
| Stripe | Better Auth subscription API | Cancel, restore, seats | User and explicit organization |
| Polar | Checkout, subscriptions, usage | Portal fallback | User and explicit organization |
| Autumn | Attach, customer subscriptions, balances | Cancel, restore, optional license seats | User |
| Creem | Checkout and active subscription | Cancel | User |
| Dodo Payments | Checkout session and subscription list | Portal fallback | User |
| Commet | Portal and current subscription | Cancel, optional feature usage and seats | User |
import {
createAutumnBillingAdapter,
createCommetBillingAdapter,
createCreemBillingAdapter,
createDodoPaymentsBillingAdapter
} from "@better-auth-ui/core/plugins/billing"
import { createAutumnClient } from "autumn-js/react"
const urls = {
successUrl: "/settings/billing?checkout=success",
cancelUrl: "/settings/billing?checkout=canceled",
returnUrl: "/settings/billing"
}
const autumnClient = createAutumnClient({
pathPrefix: "/api/auth/autumn",
includeCredentials: true
})
export const autumnAdapter = createAutumnBillingAdapter(autumnClient, {
plans,
seatLicensePlans: { pro: "team-seat" },
...urls
})
export const creemAdapter = createCreemBillingAdapter(authClient, {
plans,
products: { pro: "prod_creem_pro" },
...urls
})
export const dodoAdapter = createDodoPaymentsBillingAdapter(authClient, {
plans,
products: { pro: { type: "slug", value: "pro" } },
...urls
})
export const commetAdapter = createCommetBillingAdapter(authClient, {
plans,
planIds: { pro: "commet-plan-id" },
usage: true,
seatFeatureCode: "members",
...urls
})Enable the matching provider sub-plugins. Dodo needs checkout and portal. Commet needs portal and subscriptions, plus features or seats when you enable those adapter options.
User-only provider clients
Autumn, Creem, Dodo Payments, and Commet resolve the signed-in customer. Their browser APIs do not accept an explicit organization ID. Their adapters declare scopes.organization: false. billingPlugin rejects an organization billing configuration instead of reading active organization state.
Custom adapters
Implement BillingAdapter when you use another provider. Keep provider SDK objects inside the adapter and return only BAUI's generic plan, subscription, usage, and action types.
import type { BillingAdapter } from "@better-auth-ui/core/plugins/billing"
export const billingAdapter: BillingAdapter = {
id: "custom",
scopes: { user: true, organization: true },
supports: { cancel: true, restore: true, seats: true },
listPlans: async (scope, signal) => billingApi.listPlans(scope, signal),
getState: async (scope, signal) => billingApi.getState(scope, signal),
checkout: async (scope, input) => billingApi.checkout(scope, input),
openPortal: async (scope) => billingApi.openPortal(scope),
cancel: async (scope, subscriptionId) =>
billingApi.cancel(scope, subscriptionId),
restore: async (scope, subscriptionId) =>
billingApi.restore(scope, subscriptionId),
updateSeats: async (scope, subscriptionId, seats) =>
billingApi.updateSeats(scope, subscriptionId, seats)
}Authorize every scope
The browser can change organization IDs and slugs. Validate membership and billing permissions on the server for every adapter operation.
Headless hooks
React and Solid export the same provider-neutral hooks from @better-auth-ui/react/plugins/billing and @better-auth-ui/solid/plugins/billing:
useBillingPlansuseBillingStateuseBillingCheckoutuseBillingPortaluseCancelBillingSubscriptionuseRestoreBillingSubscriptionuseUpdateBillingSeats
Last updated on