Plugins

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/node
npm install better-auth @commet/better-auth @commet/node
yarn add better-auth @commet/better-auth @commet/node
bun add better-auth @commet/better-auth @commet/node

Preparation

Get your API key from the Commet dashboard.

.env
COMMET_API_KEY=ck_...
COMMET_ENVIRONMENT=sandbox # or production

Server Configuration

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

auth-client.ts
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.

Server
import { commet, portal } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [
    portal({ returnUrl: "/dashboard" }),
  ],
})
Client
// Redirects to Commet customer portal
await authClient.customer.portal();

Subscriptions Plugin

Manage customer subscriptions.

Server
import { commet, subscriptions } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [subscriptions()],
})
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.

Server
import { commet, features } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [features()],
})
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.

Server
import { commet, usage } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [usage()],
})
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.

Server
import { commet, seats } from "@commet/better-auth";

commet({
  client: commetClient,
  use: [seats()],
})
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

Handle Commet webhooks. This is optional since you can always query state directly.

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

Server Setup

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(),
      ],
    }),
  ],
});

Client Setup

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;

Usage in Components

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 (
    <div>
      <button onClick={handlePortal}>Manage Billing</button>
      <button onClick={checkFeature}>Use Feature</button>
    </div>
  );
}

How is this guide?