Better Auth
Better Auth Plugin for Billing and Subscriptions using Commet
Better Auth is a modern authentication library for TypeScript. This plugin integrates Commet billing directly into your Better Auth setup.
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
pnpm add better-auth @commet/better-auth @commet/nodenpm install better-auth @commet/better-auth @commet/nodeyarn add better-auth @commet/better-auth @commet/nodebun add better-auth @commet/better-auth @commet/nodePreparation
Get your API key from the Commet dashboard.
COMMET_API_KEY=ck_...
COMMET_ENVIRONMENT=sandbox # or productionServer Configuration
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
import { createAuthClient } from "better-auth/react";
import { commetClient } from "@commet/better-auth";
export const authClient = createAuthClient({
plugins: [commetClient()],
});Configuration Options
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.
import { commet, portal } from "@commet/better-auth";
commet({
client: commetClient,
use: [
portal({ returnUrl: "/dashboard" }),
],
})// Redirects to Commet customer portal
await authClient.customer.portal();Subscriptions Plugin
Manage customer subscriptions.
import { commet, subscriptions } from "@commet/better-auth";
commet({
client: commetClient,
use: [subscriptions()],
})// 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.
import { commet, features } from "@commet/better-auth";
commet({
client: commetClient,
use: [features()],
})// 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.
import { commet, usage } from "@commet/better-auth";
commet({
client: commetClient,
use: [usage()],
})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.
import { commet, seats } from "@commet/better-auth";
commet({
client: commetClient,
use: [seats()],
})// 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
Handle Commet webhooks. This is optional since you can always query state directly.
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
Server Setup
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(),
],
}),
],
});Client Setup
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;Usage in Components
"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 (
<div>
<button onClick={handlePortal}>Manage Billing</button>
<button onClick={checkFeature}>Use Feature</button>
</div>
);
}Related
How is this guide?