BETTER-AUTH. UI
Plugins

Billing

Add provider-neutral pricing, subscriptions, checkout, seats, usage, and billing management.

The billing plugin adds a complete billing tab to personal or organization settings. Its components use a generic BillingAdapter, so the UI does not depend on provider-specific objects. BAUI includes adapters for Stripe, Polar, Autumn, Creem, Dodo Payments, and Commet.

The billing view includes pricing plans, checkout, subscription status, portal access, cancellation, restoration, seats, and metered usage.

Setup

Configure billing in Better Auth

Configure one of Better Auth's supported billing plugins. Add its client plugin and apply its database schema.

See the provider guides for Stripe, Polar, Autumn, Creem, Dodo Payments, and Commet.

Create a provider-neutral adapter

lib/billing.ts
import {
  type BillingPlan,
  createStripeBillingAdapter
} from "@better-auth-ui/core/plugins/billing"

import { authClient } from "./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"
})

Price amounts use the currency's smallest unit. For example, 2000 USD means $20.00.

Register the HeroUI 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 "@better-auth-ui/heroui"
import { billingPlugin } from "@better-auth-ui/heroui/plugins/billing"
import { organizationPlugin } from "@better-auth-ui/heroui/plugins/organization"

import { billingAdapter } from "@/lib/billing"

<AuthProvider
  authClient={authClient}
  navigate={navigate}
  plugins={[
    organizationPlugin(),
    billingPlugin({
      adapter: billingAdapter,
      user: true,
      organization: true
    })
  ]}
>
  {children}
</AuthProvider>

Personal billing is enabled by default. Set organization: true to add the organization billing tab.

Polar adapter

Use the Polar adapter with the same generic plan list. Map each BAUI plan to a Polar product ID or checkout slug.

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 uses Better Auth's subscription endpoints.

Other bundled adapters

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 adapter

Implement BillingAdapter to connect another billing service. The adapter receives an explicit user or organization scope for every operation.

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

Validate organization membership and billing permissions on the server. Do not trust organization IDs or slugs from the browser.

Components and hooks

import {
  BillingSettings,
  OrganizationBillingSettings,
  UserBillingSettings
} from "@better-auth-ui/heroui/plugins/billing"

For custom views, import the provider-neutral React hooks from @better-auth-ui/react/plugins/billing.

Prop

Type

Last updated on

On this page