Skip to main content
Flowglad is a payments and billing platform that provides a stateless, developer-friendly way to handle subscriptions, usage meters, and feature gating. The Better Auth plugin seamlessly integrates Flowglad with your Better Auth authentication setup.

Features

The Flowglad Better Auth plugin provides three main capabilities:
  1. Automatic Customer Creation: When users sign up (for customerType: "user") or organizations are created (for customerType: "organization"), the plugin automatically creates a corresponding Flowglad customer.
  2. Billing API Endpoints: The plugin exposes all Flowglad billing endpoints directly on your Better Auth API at {basePath}/flowglad/*. You no longer need to mount a dedicated flowglad route handler.
  3. Customer External ID Resolution: The plugin adds a getExternalId endpoint to your Better Auth API that automatically returns the correct customer external ID based on your customerType configuration:
    • For customerType: "user": Returns session.session.userId
    • For customerType: "organization": Returns session.session.activeOrganizationId (requires the organization plugin)
    • For custom getCustomer: Returns the externalId from your custom function

Setup

Add the Flowglad plugin to your auth config

The Flowglad plugin integrates with Better Auth to automatically create customers and expose all Flowglad billing endpoints through your existing Better Auth API. You do not need to set up a separate route handler — the plugin creates endpoints at {your-better-auth-path}/flowglad/*.
lib/auth.ts
import { betterAuth } from "better-auth"
import { flowgladPlugin } from "@flowglad/nextjs/better-auth"

export const auth = betterAuth({
  // ... your Better Auth config
  plugins: [
    flowgladPlugin({
      customerType: "user",
    }),
  ],
})
Flowglad will auto-create your customers when they sign up (for customerType: "user") or when organizations are created (for customerType: "organization"). You can choose who becomes a customer: individual users, organizations, or something custom using the getCustomer function.

Add FlowgladProvider to your layout

Mount FlowgladProvider directly in your root layout with the betterAuthBasePath prop. This tells the Flowglad React SDK to route all API calls through your Better Auth endpoints instead of a standalone route handler.
Important: The betterAuthBasePath must match your Better Auth basePath configuration. If your Better Auth API is at /api/auth, set betterAuthBasePath="/api/auth".
app/layout.tsx
import { FlowgladProvider } from "@flowglad/nextjs"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <FlowgladProvider betterAuthBasePath="/api/auth">
          {children}
        </FlowgladProvider>
      </body>
    </html>
  )
}
When betterAuthBasePath is set, all Flowglad client calls (like fetching billing data, creating checkout sessions, etc.) are routed to {betterAuthBasePath}/flowglad/* — for example, /api/auth/flowglad/customers/billing.

Usage

Using the React SDK

Once you’ve set up FlowgladProvider with betterAuthBasePath, use the useBilling hook to access billing data and actions:
"use client"

import { useBilling } from "@flowglad/nextjs"

export function BillingPage() {
  const billing = useBilling()

  if (!billing.loaded) return <div>Loading...</div>
  if (!billing.customer) return <div>Please log in</div>

  return (
    <div>
      <h1>Welcome, {billing.customer.name}</h1>
      {billing.currentSubscription && (
        <p>Current plan: {billing.currentSubscription.status}</p>
      )}
      <button
        onClick={() =>
          billing.createCheckoutSession({
            priceId: "price_123",
            successUrl: window.location.href,
            cancelUrl: window.location.href,
          })
        }
      >
        Upgrade
      </button>
    </div>
  )
}
Once you’ve configured FlowgladProvider with betterAuthBasePath, all standard Flowglad React hooks work exactly as documented — the Better Auth plugin simply routes API calls through your auth endpoints instead of a standalone handler. See the React SDK documentation for detailed usage of each hook.

Server-Side: Get Customer External ID

For server-side operations, use the plugin’s getExternalId endpoint to retrieve the customer external ID:
import { auth } from "@/lib/auth"
import { headers } from "next/headers"

// Server-side usage
const { externalId } = await auth.api.getExternalId({
  headers: await headers(),
})

if (!externalId) {
  // User is not authenticated
}

// Use the externalId with your Flowglad factory function
const flowgladServer = flowglad(externalId)

Configuration Reference

FlowgladProvider

When using Better Auth integration, configure FlowgladProvider with betterAuthBasePath:
interface FlowgladProviderProps {
  /**
   * Your Better Auth API base path (e.g., "/api/auth").
   * When set, all Flowglad API calls route through Better Auth endpoints
   * at {betterAuthBasePath}/flowglad/* instead of /api/flowglad/*.
   *
   * This must match your Better Auth basePath configuration.
   */
  betterAuthBasePath?: string

  /**
   * Optional custom headers to include with all Flowglad API requests.
   */
  requestConfig?: {
    headers?: Record<string, string>
  }

  children: React.ReactNode
}

FlowgladBetterAuthPluginOptions (Server)

type FlowgladBetterAuthPluginOptions = {
  /**
   * Optional API key. If not provided, reads from FLOWGLAD_SECRET_KEY environment variable.
   */
  apiKey?: string

  /**
   * Optional base URL for Flowglad API. Defaults to https://app.flowglad.com
   */
  baseURL?: string

  /**
   * Type of customer to use. Defaults to "user".
   * - "user": Uses session.session.userId as externalId
   * - "organization": Uses session.session.activeOrganizationId (requires org plugin)
   */
  customerType?: "user" | "organization"

  /**
   * Optional function to extract customer info from Better Auth session.
   * If not provided, defaults to extracting from session.user based on customerType.
   *
   * This gives you full control over:
   * - Which ID to use (user.id, org.id, custom mapping)
   * - How to get name/email (from user, org, or custom logic)
   */
  getCustomer?: (session: InnerSession) => Promise<{
    externalId: string
    name: string
    email: string
  }>

  /**
   * Optional callback invoked when Flowglad customer auto-creation fails
   * during sign-up or organization creation hooks.
   *
   * If not provided, failures are logged with console.error and the auth
   * operation continues (best-effort customer creation).
   */
  onCustomerCreateError?: (params: {
    hook: "afterSignUp" | "afterOrganizationCreate"
    customerType: "user" | "organization"
    session: InnerSession
    error: unknown
  }) => void | Promise<void>
}

Common Patterns

Programmatic Organization Customer Creation

If you need to create a Flowglad customer for an organization programmatically (outside of the Better Auth hook), you can use the createFlowgladCustomerForOrganization helper:
import {
  createFlowgladCustomerForOrganization,
  type FlowgladBetterAuthPluginOptions,
} from "@flowglad/nextjs/better-auth"

const flowgladConfig: FlowgladBetterAuthPluginOptions = {
  customerType: "organization",
}

await createFlowgladCustomerForOrganization(flowgladConfig, {
  organizationId: "org_123",
  userId: "user_456",
  userEmail: "[email protected]",
  userName: "John Doe", // optional
  organizationName: "Acme Corp", // optional - defaults to userName
  organizationEmail: "[email protected]", // optional - defaults to userEmail
})

Custom Customer Extraction

For advanced use cases, you can provide a custom getCustomer function:
flowgladPlugin({
  getCustomer: async (session) => {
    // Custom logic: use workspace ID if available, otherwise fall back to user ID
    const workspaceId = session.user.workspaceId
    const externalId = workspaceId || session.user.id

    // Fetch additional customer data from your database
    const customerData = await db.customers.findOne({ id: externalId })

    return {
      externalId,
      name: customerData?.name || session.user.name || "Customer",
      email: customerData?.email || session.user.email || "",
    }
  },
})

Troubleshooting

Billing data not loading / API calls failing

If useBilling() returns errors or billing data doesn’t load:
  1. Verify betterAuthBasePath matches your Better Auth configuration:
    • If your Better Auth route is at /api/auth/[...all]/route.ts, use betterAuthBasePath="/api/auth"
    • The Flowglad SDK will make requests to {betterAuthBasePath}/flowglad/*
  2. Check browser network tab for failed requests to see the actual URL being called
  3. Ensure the flowgladPlugin is installed in your Better Auth config

Customer not created after sign-up

Make sure:
  • Your customerType matches your use case ("user" for B2C where users are customers, "organization" for B2B where organizations are customers)
  • The Better Auth plugin is properly configured in your auth config
  • You have FLOWGLAD_SECRET_KEY set in your environment variables
  • Check your server logs for any errors during customer creation

getExternalId returns null

This means the user is not authenticated. Make sure:
  • You’re passing the correct headers to auth.api.getExternalId()
  • The user has an active session
  • For organization customers, make sure the organization plugin is installed and the session includes activeOrganizationId (Better Auth exposes this on session.session.activeOrganizationId)

Organization customer creation issues

Make sure:
  • The organization() plugin is installed before the flowgladPlugin
  • customerType is set to "organization"
  • The session includes activeOrganizationId (Better Auth exposes this on session.session.activeOrganizationId) once the organization has been created

”NO_ACTIVE_ORGANIZATION” error

When using customerType: "organization", this error occurs if the user hasn’t selected an organization yet. Make sure:
  • The user has created or been added to an organization
  • The user has selected an active organization (Better Auth’s organization plugin manages this)
  • The activeOrganizationId is present in the session