Better Billing

Getting Started

Learn how to set up Better Billing in your application

Better Billing is a plugin-based billing system that provides type-safe subscription and payment management. This guide will walk you through setting up both the core plugin (essential) and the Stripe plugin (for payment processing).

Installation

Install the required packages:

npm install better-billing stripe

The stripe package is optional and only needed if you plan to use the Stripe plugin for payment processing.

Core Plugin Setup

Start with the core plugin, which provides essential billing functionality:

import drizzleAdapter from "@better-billing/db/adapters/drizzle";
import { betterBilling } from "better-billing";
import { corePlugin } from "better-billing/plugins/core";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";

import * as schema from "./schema";

// Initialize your database (example with Drizzle and PostgreSQL)
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
const drizzleDb = drizzle(client);

// Create Better Billing instance
const billing = betterBilling({
  serverUrl: "http://localhost:3000",
  adapter: drizzleAdapter(drizzleDb, {
    provider: "pg",
    schema, // Your Drizzle schema
  }),
  plugins: [
    corePlugin({
      subscriptionPlans: [
        {
          planName: "Free",
        },
        {
          planName: "Pro",
          trialDays: 7,
        },
        {
          planName: "Enterprise",
          trialDays: 14,
        },
      ],
    }),
  ] as const,
});

Understanding the Core Plugin:

  • Database Adapter: Connects Better Billing to your database with drizzleAdapter(drizzleDb, { provider: "pg", schema })
  • Core Plugin: Provides essential billing functionality and schema including:
    • Billable entities (users, organizations, teams)
    • Subscription management
    • Plan definitions
    • Payment methods storage
    • Invoice tracking

The core plugin is mandatory as it provides the foundational database schema and billing concepts.

Add Stripe Plugin (Optional)

To enable payment processing, add the Stripe plugin:

import drizzleAdapter from "@better-billing/db/adapters/drizzle";
import { betterBilling } from "better-billing";
import { corePlugin } from "better-billing/plugins/core";
import { stripePlugin } from "better-billing/plugins/stripe";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import Stripe from "stripe";

import * as schema from "./schema";

// Initialize Stripe
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// Initialize your database
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
const drizzleDb = drizzle(client);

// Create Better Billing instance with both plugins
const billing = betterBilling({
  serverUrl: "http://localhost:3000",
  adapter: drizzleAdapter(drizzleDb, {
    provider: "pg",
    schema,
  }),
  plugins: [
    corePlugin({
      subscriptionPlans: [
        { planName: "Free" },
        { planName: "Pro", trialDays: 7 },
        { planName: "Enterprise", trialDays: 14 },
      ],
    }),
    stripePlugin({
      stripe,
      subscriptionPlans: {
        monthly: [
          { planName: "Free", items: [{ price: "price_free_monthly" }] },
          { planName: "Pro", items: [{ price: "price_pro_monthly" }] },
          { planName: "Enterprise", items: [{ price: "price_enterprise_monthly" }] },
        ],
        yearly: [
          { planName: "Free", items: [{ price: "price_free_yearly" }] },
          { planName: "Pro", items: [{ price: "price_pro_yearly" }] },
          { planName: "Enterprise", items: [{ price: "price_enterprise_yearly" }] },
        ],
      },
      postSuccessUrl: "https://yourdomain.com/success",
      postCancelUrl: "https://yourdomain.com/cancel",
      webhookEndpointSecret: process.env.STRIPE_WEBHOOK_SECRET,
    }),
  ] as const,
});

You'll need to create the corresponding price objects in your Stripe dashboard and set up webhook endpoints. See the Stripe Setup guide for detailed instructions.

Set up API Endpoints

Set up API endpoints for webhooks and billing functionality:

Next.js:

Create a catch-all API route:

// app/api/billing/[...path]/route.ts
import { billing } from "@/lib/billing";
import { toNextJsHandler } from "better-billing/integrations/next-js";

const handler = toNextJsHandler(billing.api);

export const { GET, POST, PUT, DELETE, PATCH } = handler;

Other Frameworks:

Better Billing supports React Start, Solid Start, SvelteKit, Node.js/Express, and more. Each framework has a dedicated adapter for easy integration.

See the Framework Integrations guide for detailed setup instructions for all supported frameworks.

Test Your Setup

Test your billing setup with these examples:

Check Active Subscriptions:

const activeSubscriptions = await billing.providers.core.getBillableActiveSubscriptions({
  billableId: "user_123",
  billableType: "user",
});

// Check if user has a specific plan
const hasProPlan = activeSubscriptions.some((s) => s.planName === "Pro");

// Check for any premium features
const hasPremiumAccess = activeSubscriptions.some(s => 
  ["Pro", "Enterprise"].includes(s.planName)
);

Start a Subscription (with Stripe plugin):

// Create a checkout session for a subscription
const session = await billing.providers.stripe.startSubscriptionCheckout({
  billableId: "user_123",
  billableType: "user",
  planName: "Pro",
  cadence: "monthly",
  email: "user@example.com",
});

console.log("Checkout URL:", session.url);

Feature Gating:

// You can use plans for feature gating
function hasFeatureAccess(planName: string, feature: string): boolean {
  const featureMap = {
    "Free": ["basic_usage"],
    "Pro": ["basic_usage", "advanced_features"],
    "Enterprise": ["basic_usage", "advanced_features", "priority_support"]
  };
  
  return featureMap[planName]?.includes(feature) ?? false;
}

Next Steps