> ## Documentation Index
> Fetch the complete documentation index at: https://polar.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# BetterAuth

> Payments and Checkouts made dead simple with BetterAuth

## @polar-sh/better-auth

A [Better Auth](https://github.com/better-auth/better-auth) plugin for integrating [Polar](https://polar.sh) payments and subscriptions into your authentication flow.

### Features

* [Automatic Customer creation on signup](#automatic-customer-creation-on-signup)
* [Sync customer deletion](#sync-customer-deletion)
* [Reference System to associate purchases with organizations](#3-2-orders)
* [Checkout Integration](#checkout-plugin)
* [Event Ingestion & Customer Meters for flexible Usage Based Billing](#usage-plugin)
* [Handle Polar Webhooks securely with signature verification](#webhooks-plugin)
* [Customer Portal](#portal-plugin)

## Examples

* [With Next.js, Better Auth and Cloudflare Workers](https://github.com/polarsource/examples/tree/main/with-nextjs-better-auth-cloudflare-workers)

## Installation

Install the required Better Auth and Polar packages using the following command:

<Tabs>
  <Tab title="npm">
    ```bash Terminal theme={null}
    npm install better-auth @polar-sh/better-auth @polar-sh/sdk
    ```
  </Tab>

  <Tab title="yarn">
    ```bash Terminal theme={null}
    yarn add better-auth @polar-sh/better-auth @polar-sh/sdk
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash Terminal theme={null}
    pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk
    ```
  </Tab>

  <Tab title="bun">
    ```bash Terminal theme={null}
    bun add better-auth @polar-sh/better-auth @polar-sh/sdk
    ```
  </Tab>
</Tabs>

## Integrate Polar with BetterAuth

<Steps>
  <Step title="Configure Polar Access Token">
    Go to your Polar Organization Settings, create an Organization Access Token, and add it to the environment variables of your application.

    ```bash .env theme={null}
    POLAR_ACCESS_TOKEN=...
    ```
  </Step>

  <Step title="Configure BetterAuth Server">
    The Polar plugin comes with it's own set of plugins to add functionality to your stack:

    * **checkout** - Enable seamless checkout integration
    * **portal** - Make it possible for your customers to manage their orders, subscriptions & benefits
    * **usage** - List customer meters & ingest events for Usage Based Billing
    * **webhooks** - Listen for relevant Polar webhooks

    ```typescript icon="square-js" auth.ts theme={null}
    import { betterAuth } from "better-auth";
    import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth"; // [!code ++]
    import { Polar } from "@polar-sh/sdk"; // [!code ++]

    const polarClient = new Polar({ // [!code ++]
        accessToken: process.env.POLAR_ACCESS_TOKEN, // [!code ++]
        // Use 'sandbox' if you're using the Polar Sandbox environment
        // Remember that access tokens, products, etc. are completely separated between environments.
        // Access tokens obtained in Production are for instance not usable in the Sandbox environment.
        server: 'sandbox' // [!code ++]
    }); // [!code ++]

    const auth = betterAuth({
        // ... Better Auth config
        plugins: [
            polar({ // [!code ++]
                client: polarClient, // [!code ++]
                createCustomerOnSignUp: true, // [!code ++]
                use: [ // [!code ++]
                    checkout({ // [!code ++]
                        products: [ // [!code ++]
                            { // [!code ++]
                                productId: "123-456-789", // ID of Product from Polar Dashboard // [!code ++]
                                slug: "pro" // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro // [!code ++]
                            } // [!code ++]
                        ], // [!code ++]
                        successUrl: "/success?checkout_id={CHECKOUT_ID}", // [!code ++]
                        authenticatedUsersOnly: true // [!code ++]
                    }), // [!code ++]
                    portal(), // [!code ++]
                    usage(), // [!code ++]
                    webhooks({ // [!code ++]
                        secret: process.env.POLAR_WEBHOOK_SECRET, // [!code ++]
                        onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes // [!code ++]
                        onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) // [!code ++]
                        ...  // Over 25 granular webhook handlers // [!code ++]
                        onPayload: (payload) => // Catch-all for all events // [!code ++]
                    }) // [!code ++]
                ], // [!code ++]
            }) // [!code ++]
        ]
    });
    ```

    #### Polar Plugin Configuration Options

    ```typescript theme={null}
    // ...

    const auth = betterAuth({
      // ... Better Auth config
      plugins: [
        polar({
          client: polarClient, // [!code ++]
          createCustomerOnSignUp: true, // [!code ++]
          getCustomerCreateParams: ({ user }, request) => ({ // [!code ++]
            metadata: { // [!code ++]
              myCustomProperty: 123, // [!code ++]
            }, // [!code ++]
          }), // [!code ++]
          use: [ // [!code ++]
            // This is where you add Polar plugins // [!code ++]
          ], // [!code ++]
        }),
      ],
    });
    ```

    * `client` (required): Polar SDK client instance
    * `createCustomerOnSignUp` (optional): Automatically create a Polar customer when a user signs up
    * `getCustomerCreateParams` (optional): Custom function to provide additional customer creation metadata
    * `use` (optional): Array of Polar plugins to enable specific functionality (checkout, portal, usage, and webhooks)
  </Step>

  <Step title="Configure BetterAuth Client">
    You will be using the BetterAuth Client to interact with the Polar functionalities.

    ```typescript icon="square-js" auth-client.ts theme={null}
    import { createAuthClient } from "better-auth/react";
    import { polarClient } from "@polar-sh/better-auth/client"; // [!code ++]
    import { organizationClient } from "better-auth/client/plugins"; // [!code ++]

    // All Polar plugins, etc. should be attached to BetterAuth server
    export const authClient = createAuthClient({ // [!code ++]
      plugins: [polarClient()], // [!code ++]
    }); // [!code ++]
    ```
  </Step>
</Steps>

## Automatic Customer creation on signup

Enable the `createCustomerOnSignUp` [Polar plugin configuration option](#polar-plugin-configuration-options) to automatically create a new Polar Customer when a new User is added in the BetterAuth database.

All new customers are created with an associated `externalId`, i.e. the ID of your User in the Database. This skips any Polar to User mapping in your database.

## Sync Customer deletion

To add user deletion logic with external Polar customer deletion in BetterAuth, extend the user config of your betterAuth setup with the deleteUser option and an afterDelete hook. Here’s how to integrate this alongside your Polar plugin and automatic customer creation:

```typescript icon="square-js" Customer Deletion Example theme={null}
const auth = betterAuth({
  user: {
    // [!code ++]
    deleteUser: {
      // [!code ++]
      enabled: true, // [!code ++]
      afterDelete: async (user, request) => {
        // [!code ++]
        await polar.customers.deleteExternal({
          // [!code ++]
          externalId: user.id, // [!code ++]
        }); // [!code ++]
      }, // [!code ++]
    }, // [!code ++]
  }, // [!code ++]
});
```

## Checkout Plugin

[Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/checkout.ts)

To support [checkouts](/features/checkout/links) in your app, you would pass the `checkout` plugin in the `use` property.

The checkout plugin accepts the following configuration options:

* **`products`** (optional): An array of product mappings or a function that returns them asynchronously. Each mapping contains a `productId` and a `slug` that allows you to reference products by a friendly slug instead of their full ID.
* **`successUrl`** (optional): The relative path or absolute URL where customers will be redirected after a successful checkout completion. You can use the `{CHECKOUT_ID}` placeholder in the URL to include the checkout session ID in the redirect.
* **`returnUrl`** (optional): An optional URL which renders a back-button in the Checkout.
* **`authenticatedUsersOnly`** (optional): A boolean flag that controls whether checkout sessions require user authentication. When set to `true`, only authenticated users can initiate checkouts and the customer information will be automatically associated with the authenticated user. When `false`, anonymous checkouts are allowed.
* **`theme`** (optional): A string that can be used to enforce the theme of the checkout page. Can be either `light` or `dark`.

<Steps>
  <Step title="Use Checkout Plugin">
    Update the `use` property of the Polar plugin for BetterAuth client to have the `checkout` plugin.

    ```typescript icon="square-js" Checkout Plugin Example theme={null}
    import {
      polar,
      checkout // [!code ++]
    } from "@polar-sh/better-auth";

    const auth = betterAuth({
        // ... Better Auth config
        plugins: [
            polar({
                ...
                use: [
                    checkout({ // [!code ++]
                        // Optional field - will make it possible to pass a slug to checkout instead of Product ID
                        products: [ { productId: "123-456-789", slug: "pro" } ], // [!code ++]
                        // Relative path or absolute URL to redirect to when checkout is successfully completed
                        successUrl: "/success?checkout_id={CHECKOUT_ID}", // [!code ++]
                        // Whether you want to allow unauthenticated checkout sessions or not
                        authenticatedUsersOnly: true, // [!code ++]
                        // An optional URL which renders a back-button in the Checkout
                        returnUrl: "https://myapp.com" // [!code ++]
                    }) // [!code ++]
                ],
            })
        ]
    });
    ```
  </Step>

  <Step title="Create checkouts using BetterAuth client">
    When the `checkout` plugin is passed, you are then able to initialize Checkout Sessions using the `checkout` method on the BetterAuth client. This will redirect the user to the product's checkout link.

    The `checkout` method accepts the following properties:

    * **`products`** (optional): An array of Polar Product IDs.
    * **`slug`** (optional): A string that can be used as a reference to the `products` defined in the Checkout config
    * **`referenceId`** (optional): An identifier that will be saved in the metadata of the checkout, order & subscription object

    ```typescript icon="square-js" BetterAuth Checkout with Polar Example theme={null}
    await authClient.checkout({
      // Polar Product IDs
      products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], // [!code ++]
      // OR
      // if "products" in passed in the checkout plugin's config, you may pass the slug
      // slug: "pro", // [!code ++]
    });
    ```

    This plugin supports the Organization plugin. If you pass the organization ID to the Checkout referenceId, you will be able to keep track of purchases made from organization members.

    ```typescript icon="square-js" BetterAuth Checkout with Polar Organization Example theme={null}
    const organizationId = (await authClient.organization.list())?.data?.[0]?.id,

    await authClient.checkout({
        // Any Polar Product ID can be passed here
        products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
        // Or, if you setup "products" in the Checkout Config, you can pass the slug
        slug: 'pro',
        // Reference ID will be saved as `referenceId` in the metadata of the checkout, order & subscription object
        referenceId: organizationId
    });
    ```
  </Step>
</Steps>

## Usage Plugin

[Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/usage.ts)

A plugin for Usage Based Billing that allows you to [ingest events](#event-ingestion) from your application and list the [authenticated user's Usage Meter](#customer-meters).

To enable [usage based billing](/integrate/sdk/adapters/better-auth) in your app, you would pass the `usage` plugin in the `use` property.

```typescript icon="square-js" Usage Plugin Example theme={null}
import {
  polar, checkout, portal,
  usage // [!code ++]
} from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth config
    plugins: [
        polar({
            ...
            use: [
                checkout(...),
                portal(),
                usage() // [!code ++]
            ],
        })
    ]
});
```

### 1. Event Ingestion

Polar's Usage Based Billing builds entirely on event ingestion. Ingest events from your application, create Meters to represent that usage, and add metered prices to Products to charge for it.

<Warning>
  **Ingest events from your server, not from the browser.** Events drive billing, so the client must never be trusted to decide what is consumed. Always ingest from the same server-side handler that performs the metered action (e.g. the route that generated the AI video, processed the upload, etc.) so that the recorded usage cannot be forged or bypassed.
</Warning>

Because `createCustomerOnSignUp: true` sets the Polar customer's `externalId` to the BetterAuth user ID, you can pass `externalCustomerId: session.user.id` when ingesting — no extra lookup is needed.

The example below uses the Polar SDK directly from a server route handler. The same `polarClient` instance you configured in `auth.ts` is reused:

```typescript icon="square-js" app/api/ai/video/route.ts (Next.js App Router) theme={null}
import { auth } from "@/lib/auth";
import { polarClient } from "@/lib/polar";
import { headers } from "next/headers";

export async function POST(request: Request) {
  const session = await auth.api.getSession({ headers: await headers() });

  if (!session) {
    return new Response("Unauthorized", { status: 401 });
  }

  // Run the metered work on the server
  const { video, tokensConsumed } = await makeNewVideo(request);

  // Ingest the resulting usage against the authenticated user
  await polarClient.events.ingest({
    events: [
      {
        name: "ai-video",
        externalCustomerId: session.user.id,
        metadata: {
          tokensConsumed,
        },
      },
    ],
  });

  return Response.json({ video });
}
```

The `events.ingest` method accepts:

* `name` (string): The name of the event to ingest. For example, `ai_usage`, `video_streamed` or `file_uploaded`.
* `externalCustomerId` (string): The BetterAuth user ID. Pass `session.user.id` from the server-side session.
* `metadata` (object): A record of key-value pairs that describe the event. Values can be strings, numbers, or booleans. Use this to store information that can be filtered on or used to compute usage — duration, token count, file size, etc.

#### Client-side ingestion endpoint

The `usage` plugin also registers a BetterAuth endpoint that is callable from the client as `authClient.usage.ingestion(...)`. It forwards to Polar from the BetterAuth server and attaches the authenticated user automatically:

```typescript icon="square-js" auth-client.ts theme={null}
const { data: ingested } = await authClient.usage.ingestion({
  event: "file-uploads",
  metadata: {
    uploadedFiles: 12,
  },
});
```

<Warning>
  This endpoint trusts whatever the client sends it, so a user can call it from their browser and claim any usage they want. Only use it for events that genuinely originate on the client and that you are comfortable not being authoritative (e.g. analytics-like signals). For billable usage, stick to server-side ingestion as shown above.
</Warning>

### 2. Customer Meters

A method to list the authenticated user's Usage Meters (aka Customer Meters). A Customer Meter contains all the information about their consumption on your defined meters.

The `meters` method of the `usage` plugin accepts the following parameters:

* `page` (number): The page number for pagination (starts from 1).

* `limit` (number): The maximum number of meters to return per page.

```typescript icon="square-js" Customer Meters with Usage Plugin Example theme={null}
const { data: customerMeters } = await authClient.usage.meters.list({
  query: {
    page: 1,
    limit: 10,
  },
});
```

The `meters` method returns the following fields in the response object:

* **Customer Information**: Details about the authenticated customer
* **Meter Information**: Configuration and settings of the usage meter
* **Customer Meter Information**:
  * **Consumed Units**: Total units consumed by the customer
  * **Credited Units**: Total units credited to the customer
  * **Balance**: The balance of the meter, i.e. the difference between credited and consumed units.

## Webhooks Plugin

[Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/webhooks.ts)

The `webhooks` plugin can be used to capture incoming events from your Polar organization.

To set up the Polar `webhooks` plugin with the BetterAuth client, follow the steps below:

<Steps>
  <Step title="Configure Webhook Endpoints in Polar">
    Configure a Webhook endpoint in your Polar Organization Settings page by following [this guide](/integrate/webhooks/endpoints). Webhook endpoint is configured at `/api/auth/polar/webhooks`.
  </Step>

  <Step title="Add the Webhook Secret">
    Add the obtained webhook secret to your application environment as an environment variable (to be used as `process.env.POLAR_WEBHOOK_SECRET`):

    ```bash .env theme={null}
    POLAR_WEBHOOK_SECRET="..."
    ```
  </Step>

  <Step title="Use Webhooks Plugin in BetterAuth client">
    Pass the `webhooks` plugin in the `use` property.

    ```typescript icon="square-js" Webhooks Plugin Example theme={null}
    import {
      polar,
      webhooks // [!code ++]
    } from "@polar-sh/better-auth";

    const auth = betterAuth({
        // ... Better Auth config
        plugins: [
            polar({
                ...
                use: [
                    webhooks({ // [!code ++]
                        secret: process.env.POLAR_WEBHOOK_SECRET, // [!code ++]
                        onCustomerStateChanged: (payload) => // Triggered when anything regarding a customer changes // [!code ++]
                        onOrderPaid: (payload) => // Triggered when an order was paid (purchase, subscription renewal, etc.) // [!code ++]
                        ...  // Over 25 granular webhook handlers // [!code ++]
                        onPayload: (payload) => // Catch-all for all events // [!code ++]
                    }) // [!code ++]
                ],
            })
        ]
    });
    ```
  </Step>
</Steps>

The `webhooks` plugin allows you to invoke handlers for all Polar webhook events:

* `onPayload` - Catch-all handler for any incoming Webhook event
* `onCheckoutCreated` - Triggered when a checkout is created
* `onCheckoutUpdated` - Triggered when a checkout is updated
* `onOrderCreated` - Triggered when an order is created
* `onOrderPaid` - Triggered when an order is paid
* `onOrderRefunded` - Triggered when an order is refunded
* `onRefundCreated` - Triggered when a refund is created
* `onRefundUpdated` - Triggered when a refund is updated
* `onSubscriptionCreated` - Triggered when a subscription is created
* `onSubscriptionUpdated` - Triggered when a subscription is updated
* `onSubscriptionActive` - Triggered when a subscription becomes active
* `onSubscriptionCanceled` - Triggered when a subscription is canceled
* `onSubscriptionRevoked` - Triggered when a subscription is revoked
* `onSubscriptionUncanceled` - Triggered when a subscription cancellation is reversed
* `onProductCreated` - Triggered when a product is created
* `onProductUpdated` - Triggered when a product is updated
* `onOrganizationUpdated` - Triggered when an organization is updated
* `onBenefitCreated` - Triggered when a benefit is created
* `onBenefitUpdated` - Triggered when a benefit is updated
* `onBenefitGrantCreated` - Triggered when a benefit grant is created
* `onBenefitGrantUpdated` - Triggered when a benefit grant is updated
* `onBenefitGrantRevoked` - Triggered when a benefit grant is revoked
* `onCustomerCreated` - Triggered when a customer is created
* `onCustomerUpdated` - Triggered when a customer is updated
* `onCustomerDeleted` - Triggered when a customer is deleted
* `onCustomerStateChanged` - Triggered when a customer state changes

## Portal Plugin

[Source code](https://github.com/polarsource/polar-adapters/blob/main/packages/polar-betterauth/src/plugins/portal.ts)

A plugin which enables customer management of their purchases, orders and subscriptions.

```typescript icon="square-js" Portal Plugin Example theme={null}
import {
  polar, checkout,
  portal // [!code ++]
} from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth config
    plugins: [
        polar({
            ...
            use: [
                checkout(...),
                portal({
                  returnUrl: "https://myapp.com", // An optional URL which renders a back-button in the Customer Portal
                }) // [!code ++]
            ],
        })
    ]
});
```

The `portal` plugin gives the BetterAuth Client a set of customer management methods, scoped under `authClient.customer` object.

### 1. Customer Portal Management

The following method will redirect the user to the Polar Customer Portal, where they can see their orders, purchases, subscriptions, benefits, etc.

```typescript icon="square-js" Open Customer Portal Example theme={null}
await authClient.customer.portal();
```

### 2. Customer State

The portal plugin also adds a convenient method to retrieve the Customer State.

```typescript icon="square-js" Retrieve Customer State Example theme={null}
const { data: customerState } = await authClient.customer.state();
```

The customer state object contains:

* All the data about the customer.
* The list of their active subscriptions.
  <Note>
    This does not include subscriptions done by a parent organization. See the
    subscription list-method below for more information.
  </Note>
* The list of their granted benefits.
* The list of their active meters, with their current balance.

Using the customer state object, you can determine whether to provision access for the user to your service.

Learn more about the Polar Customer State [in the Polar Docs](https://polar.sh/docs/integrate/customer-state).

### 3. Benefits, Orders & Subscriptions

The portal plugin adds the following 3 convenient methods for listing benefits, orders & subscriptions relevant to the authenticated user/customer.

#### 3.1 Benefits

This method only lists granted benefits for the authenticated user/customer.

```typescript icon="square-js" List User Benefits Example theme={null}
const { data: benefits } = await authClient.customer.benefits.list({
  query: {
    page: 1,
    limit: 10,
  },
});
```

#### 3.2 Orders

This method lists orders like purchases and subscription renewals for the authenticated user/customer.

```typescript icon="square-js" List User Orders Example theme={null}
const { data: orders } = await authClient.customer.orders.list({
  query: {
    page: 1,
    limit: 10,
    productBillingType: "one_time", // or 'recurring'
  },
});
```

Using the Organization ID as the `referenceId` you can retrieve all the subscriptions associated with that organization (instead of the user).

To figure out if a user should have access, pass the user's organization ID to see if there is an active subscription for that organization.

```typescript icon="square-js" List Organization Subscriptions Example theme={null}
const organizationId = (await authClient.organization.list())?.data?.[0]?.id,

const { data: subscriptions } = await authClient.customer.orders.list({
    query: {
	    page: 1,
		limit: 10,
		active: true,
        referenceId: organizationId
    },
});

const userShouldHaveAccess = subscriptions.some(
    sub => // Your logic to check subscription product or whatever.
)
```

#### 3.3 Subscriptions

This method lists the subscriptions associated with authenticated user/customer.

```typescript icon="square-js" List User Subscriptions Example theme={null}
const { data: subscriptions } = await authClient.customer.subscriptions.list({
  query: {
    page: 1,
    limit: 10,
    active: true,
  },
});
```

<Danger>
  This will not return subscriptions made by a parent organization to the
  authenticated user.
</Danger>
