BETTER-AUTH. UI
Plugins

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/billing

This 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.

lib/billing.ts
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.

components/providers.tsx
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.

lib/billing.ts
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.

AdapterCheckout and stateDirect actionsScope
StripeBetter Auth subscription APICancel, restore, seatsUser and explicit organization
PolarCheckout, subscriptions, usagePortal fallbackUser and explicit organization
AutumnAttach, customer subscriptions, balancesCancel, restore, optional license seatsUser
CreemCheckout and active subscriptionCancelUser
Dodo PaymentsCheckout session and subscription listPortal fallbackUser
CommetPortal and current subscriptionCancel, optional feature usage and seatsUser
lib/billing.ts
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:

  • useBillingPlans
  • useBillingState
  • useBillingCheckout
  • useBillingPortal
  • useCancelBillingSubscription
  • useRestoreBillingSubscription
  • useUpdateBillingSeats

Last updated on

On this page