Commet

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

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

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 (Optional)

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

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

On this page