@polar-sh/better-auth

A Better Auth plugin for integrating Polar payments and subscriptions into your authentication flow.

Features

Installation

Install the BetterAuth and Polar required libraries using the following command:

npm install better-auth @polar-sh/better-auth @polar-sh/sdk

Integrate Polar with BetterAuth

1

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.

.env
POLAR_ACCESS_TOKEN=...
2

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
BetterAuth Server with Polar Example
import { betterAuth } from "better-auth";
import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth"; 
import { Polar } from "@polar-sh/sdk"; 

const polarClient = new Polar({ 
    accessToken: process.env.POLAR_ACCESS_TOKEN, 
    // 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'
}); 

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

Polar Plugin Configuration Options

// ...

const auth = betterAuth({
  // ... Better Auth config
  plugins: [
    polar({
      client: polarClient, 
      createCustomerOnSignUp: true, 
      getCustomerCreateParams: ({ user }, request) => ({ 
        metadata: { 
          myCustomProperty: 123, 
        }, 
      }), 
      use: [ 
        // This is where you add Polar plugins
      ], 
    }),
  ],
});
  • 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)
3

Configure BetterAuth Client

You will be using the BetterAuth Client to interact with the Polar functionalities.

BetterAuth Client with Polar Example
import { createAuthClient } from "better-auth/react";
import { polarClient } from "@polar-sh/better-auth"; 
import { organizationClient } from "better-auth/client/plugins"; 

// All Polar plugins, etc. should be attached to BetterAuth server
export const authClient = createAuthClient({ 
  plugins: [polarClient()], 
}); 

Automatic Customer creation on signup

Enable the createCustomerOnSignUp Polar plugin configuration option 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.

Checkout Plugin

Source code

To support checkouts 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 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.

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

1

Use Checkout Plugin

Update the use property of the Polar plugin for BetterAuth client to have the checkout plugin.

Checkout Plugin Example
import { 
  polar,
  checkout
} from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth config
    plugins: [
        polar({
            ...
            use: [
                checkout({ 
                    // Optional field - will make it possible to pass a slug to checkout instead of Product ID
                    products: [ { productId: "123-456-789", slug: "pro" } ], 
                    // Relative URL to return to when checkout is successfully completed
                    successUrl: "/success?checkout_id={CHECKOUT_ID}", 
                    // Wheather you want to allow unauthenticated checkout sessions or not
                    authenticatedUsersOnly: true
                }) 
            ],
        })
    ]
});
2

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
BetterAuth Checkout with Polar Example
await authClient.checkout({
  // Polar Product IDs
  products: ["e651f46d-ac20-4f26-b769-ad088b123df2"], 
  // OR
  // if "products" in passed in the checkout plugin's config, you may pass the slug
  // slug: "pro",
});

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.

BetterAuth Checkout with Polar Organization Example
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
});

Usage Plugin

Source code

A plugin for Usage Based Billing that allows you to ingest events from your application and list the authenticated user’s Usage Meter.

To enable usage based billing in your app, you would pass the usage plugin in the use property.

Usage Plugin Example
import {
  polar, checkout, portal,
  usage
} from "@polar-sh/better-auth";

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

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.

The ingestion method of the usage plugin accepts the following parameters:

  • event (string): The name of the event to ingest. For example, ai_usage, video_streamed or file_uploaded.

  • metadata (object): A record containing key-value pairs that provide additional context about the event. Values can be strings, numbers, or booleans. This is useful for storing information that can be used to filter the events or compute the actual usage. For example, you can store the duration of the video streamed or the size of the file uploaded.

The authenticated user is automatically associated with the ingested event.

Event Ingestion with Usage Plugin Example
const { data: ingested } = await authClient.usage.ingestion({
  event: "file-uploads",
  metadata: {
    uploadedFiles: 12,
  },
});

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.

Customer Meters with Usage Plugin Example
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

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:

1

Configure Webhook Endpoints in Polar

Configure a Webhook endpoint in your Polar Organization Settings page by following this guide. Webhook endpoint is configured at /polar/webhooks.

2

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):

.env
POLAR_WEBHOOK_SECRET="..."
3

Use Webhooks Plugin in BetterAuth client

Pass the webhooks plugin in the use property.

Webhooks Plugin Example
import {
  polar,
  webhooks
} from "@polar-sh/better-auth";

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

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 is created

Portal Plugin

Source code

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

Portal Plugin Example
import {
  polar, checkout,
  portal
} from "@polar-sh/better-auth";

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

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.

Open Customer Portal Example
await authClient.customer.portal();

2. Customer State

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

Retrieve Customer State Example
const { data: customerState } = await authClient.customer.state();

The customer state object contains:

  • All the data about the customer.
  • The list of their active subscriptions.

    This does not include subscriptions done by a parent organization. See the subscription list-method below for more information.

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

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.

List User Benefits Example
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.

List User Orders Example
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.

List Organization Subscriptions Example
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.

List User Subscriptions Example
const { data: subscriptions } = await authClient.customer.subscriptions.list({
  query: {
    page: 1,
    limit: 10,
    active: true,
  },
});

This will not return subscriptions made by a parent organization to the authenticated user.