--- title: Commet description: Better Auth Plugin for Billing and Subscriptions using Commet --- [Commet](https://commet.co) is the billing and payments solution for SaaS and AI products. As a Merchant of Record, Commet handles subscriptions, usage-based billing, tax compliance, and global payments—so you can start monetizing in minutes. This plugin is maintained by the Commet team. For bugs, issues or feature requests, please contact [Commet support](https://commet.co). ## Features - Automatic customer creation on signup - Customer Portal for self-service billing management - Subscription management (get, change plan, cancel) - Feature access control (boolean, metered, seats) - Usage tracking for metered billing - Seat management for per-user pricing - Optional webhook handling with signature verification ## Installation ```bash pnpm add better-auth @commet/better-auth @commet/node ``` ## Preparation Get your API key from the [Commet dashboard](https://sandbox.commet.co). ```bash title=".env" COMMET_API_KEY=ck_... COMMET_ENVIRONMENT=sandbox # or production ``` ## Server Configuration ```typescript title="auth.ts" import { betterAuth } from "better-auth"; import { commet, portal, subscriptions, features, usage, seats, } from "@commet/better-auth"; import { Commet } from "@commet/node"; const commetClient = new Commet({ apiKey: process.env.COMMET_API_KEY, environment: process.env.COMMET_ENVIRONMENT, // 'sandbox' or 'production' }); export const auth = betterAuth({ // ... your config plugins: [ commet({ client: commetClient, createCustomerOnSignUp: true, use: [ portal(), subscriptions(), features(), usage(), seats(), ], }), ], }); ``` ## Client Configuration ```typescript title="auth-client.ts" import { createAuthClient } from "better-auth/react"; import { commetClient } from "@commet/better-auth"; export const authClient = createAuthClient({ plugins: [commetClient()], }); ``` ## Configuration Options ```typescript commet({ client: commetClient, // Required: Commet SDK instance createCustomerOnSignUp: true, // Auto-create customer on signup getCustomerCreateParams: ({ user }) => ({ legalName: user.name, metadata: { source: "web" }, }), use: [/* plugins */], }) ``` When `createCustomerOnSignUp` is enabled, a Commet customer is automatically created with `externalId` set to the user's ID. No database mapping required. ## Portal Plugin Redirects users to the Commet customer portal for self-service billing management. ```typescript title="Server" import { commet, portal } from "@commet/better-auth"; commet({ client: commetClient, use: [ portal({ returnUrl: "/dashboard" }), ], }) ``` ```typescript title="Client" // Redirects to Commet customer portal await authClient.customer.portal(); ``` ## Subscriptions Plugin Manage customer subscriptions. ```typescript title="Server" import { commet, subscriptions } from "@commet/better-auth"; commet({ client: commetClient, use: [subscriptions()], }) ``` ```typescript title="Client" // Get current subscription const { data: subscription } = await authClient.subscription.get(); // Change plan await authClient.subscription.changePlan({ subscriptionId: "sub_xxx", planCode: "enterprise", billingInterval: "yearly", }); // Cancel subscription await authClient.subscription.cancel({ subscriptionId: "sub_xxx", reason: "Too expensive", immediate: false, // Cancel at period end }); ``` ## Features Plugin Check feature access for the authenticated user. ```typescript title="Server" import { commet, features } from "@commet/better-auth"; commet({ client: commetClient, use: [features()], }) ``` ```typescript title="Client" // List all features const { data: featuresList } = await authClient.features.list(); // Get specific feature const { data: feature } = await authClient.features.get("api_calls"); // Check if feature is enabled (boolean) const { data: check } = await authClient.features.check("sso"); // Check if user can use one more unit (metered) const { data: canUse } = await authClient.features.canUse("api_calls"); // Returns: { allowed: boolean, willBeCharged: boolean } ``` ## Usage Plugin Track usage events for metered billing. ```typescript title="Server" import { commet, usage } from "@commet/better-auth"; commet({ client: commetClient, use: [usage()], }) ``` ```typescript title="Client" await authClient.usage.track({ eventType: "api_call", value: 1, idempotencyKey: `evt_${Date.now()}`, properties: { endpoint: "/api/generate" }, }); ``` The authenticated user is automatically associated with the event. ## Seats Plugin Manage seat-based licenses. ```typescript title="Server" import { commet, seats } from "@commet/better-auth"; commet({ client: commetClient, use: [seats()], }) ``` ```typescript title="Client" // List all seat balances const { data: seatBalances } = await authClient.seats.list(); // Add seats await authClient.seats.add({ seatType: "member", count: 5 }); // Remove seats await authClient.seats.remove({ seatType: "member", count: 2 }); // Set exact count await authClient.seats.set({ seatType: "admin", count: 3 }); // Set all seat types at once await authClient.seats.setAll({ admin: 2, member: 10, viewer: 50 }); ``` ## Webhooks Plugin (Optional) Handle Commet webhooks. This is optional since you can always query state directly. ```typescript title="Server" import { commet, webhooks } from "@commet/better-auth"; commet({ client: commetClient, use: [ webhooks({ secret: process.env.COMMET_WEBHOOK_SECRET, onPayload: (payload) => { // Catch-all handler }, onSubscriptionCreated: (payload) => {}, onSubscriptionActivated: (payload) => {}, onSubscriptionCanceled: (payload) => {}, onSubscriptionUpdated: (payload) => {}, }), ], }) ``` Configure the webhook endpoint in your Commet dashboard: `/api/auth/commet/webhooks` ## Full Example ```typescript title="auth.ts" import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { commet as commetPlugin, portal, subscriptions, features, usage, seats, } from "@commet/better-auth"; import { Commet } from "@commet/node"; import { db } from "./db"; import * as schema from "./schema"; const commetClient = new Commet({ apiKey: process.env.COMMET_API_KEY!, environment: process.env.COMMET_ENVIRONMENT as "sandbox" | "production", }); export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema }), emailAndPassword: { enabled: true }, plugins: [ commetPlugin({ client: commetClient, createCustomerOnSignUp: true, getCustomerCreateParams: ({ user }) => ({ legalName: user.name, }), use: [ portal({ returnUrl: "/dashboard" }), subscriptions(), features(), usage(), seats(), ], }), ], }); ``` ```typescript title="auth-client.ts" import { createAuthClient } from "better-auth/react"; import { commetClient } from "@commet/better-auth"; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL, plugins: [commetClient()], }); export const { signIn, signUp, signOut, useSession } = authClient; ``` ```tsx title="dashboard.tsx" "use client"; import { authClient } from "@/lib/auth-client"; export function BillingSection() { const handlePortal = async () => { await authClient.customer.portal(); }; const checkFeature = async () => { const { data } = await authClient.features.canUse("api_calls"); if (data?.allowed) { // Proceed with action await authClient.usage.track({ eventType: "api_call" }); } }; return (
); } ```