@polar-sh/better-auth
A Better Auth plugin for integrating Polar payments and subscriptions into your authentication flow.Features
- Automatic Customer creation on signup
- Sync customer deletion
- Organization billing
- Checkout Integration
- Event Ingestion & Customer Meters for flexible Usage Based Billing
- Handle Polar Webhooks securely with signature verification
- Customer Portal
Examples
Installation
Install the required Better Auth and Polar packages using the following command:- npm
- yarn
- pnpm
- bun
Terminal
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
2
Configure BetterAuth Server
The Polar plugin comes with it’s own set of plugins to add functionality to your stack:Responses and webhook payloads use the SDK’s snake_case field names.
- 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
auth.ts
Polar Plugin Configuration Options
client(required): Polar core client, created withcreatePolarCorefrom@polar-sh/sdk/2026-10use(required): Array of Polar plugins to enable specific functionality (checkout, portal, usage, and webhooks). Pass at least one plugin.createCustomerOnSignUp(optional): Automatically create a Polar customer when a user signs upgetCustomerCreateParams(optional): Async function that returns additional metadata for new customersexperimental_organizationSync(optional): Mirror Better Auth organizations to Polar team customers. See Organization billing.
customers or events. To call the Polar API yourself, import the operation you need and pass the client to it:3
Configure BetterAuth Client
You will be using the BetterAuth Client to interact with the Polar functionalities.
auth-client.ts
organizationClient() is only needed if you use Better Auth’s organization plugin.Automatic Customer creation on signup
Enable thecreateCustomerOnSignUp Polar plugin configuration option to automatically create a new Polar Customer when a new User is added in the BetterAuth database.
Customer creation runs after Better Auth inserts the user, so the initial Polar creation request includes external_id: user.id. Applications that reject user creation in a before database hook do not create a Polar customer.
getCustomerCreateParams runs only when a new customer is needed and receives the persisted user. It can add metadata; the user ID, email, and name remain authoritative. An existing customer with the same email and no external ID is linked once. An existing customer linked to the same user is reused; a different external ID produces a conflict without reassigning the customer.
When a user’s email or name changes in Better Auth, the plugin updates the Polar customer. Anonymous users are skipped.
Sync Customer deletion
WithcreateCustomerOnSignUp enabled, the plugin also deletes the Polar customer whose external ID matches the user when that user is deleted in Better Auth. You don’t need your own afterDelete hook.
Organization billing
The plugin supports two ways to bill Better Auth organizations.Metadata-based billing with reference_id
Pass the organization ID as reference_id when creating a checkout. The checkout stays attached to the user’s personal Polar customer, and the ID is saved as metadata.referenceId on the checkout, order and subscription.
Checkout for an organization
reference_id:
List Organization Subscriptions Example
Team customers with experimental_organizationSync
With Better Auth’s organization() plugin and experimental_organizationSync: { enabled: true }, each Better Auth organization is mirrored to a Polar team customer. The organization ID becomes the team customer’s external ID, and members are mirrored as Polar members with their user ID as external ID.
auth.ts
experimental_organizationSync accepts:
enabled(required): Turn synchronization ongetTeamCustomerCreateParams(optional): Add metadata, billing details or other fields to new team customersmapBetterAuthRoleToPolarRole(optional): Map non-owner Better Auth roles tomemberorbilling_managersyncSeats(optional): Size and assign seat-based subscriptions from the Better Auth roster. Requires thewebhooksplugin and thesubscription.createdandsubscription.activeevents.selectSeatProductsForMember(optional): Choose which seat products each member receives whensyncSeatsis enabled
organization_id to authClient.checkout(), organizationId to authClient.usage.ingest(), and query: { organizationId } to the portal, state, benefits, subscriptions, orders and meters methods. The plugin verifies the user’s membership before calling Polar, and checkout also requires a billing-capable role.
The access token needs the customers:read, customers:write, members:read and members:write scopes. See the package README for the full role mapping and seat management behaviour.
Checkout Plugin
Source code To support checkouts in your app, you would pass thecheckout 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 aproductIdand aslugthat 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. Relative paths resolve against your auth server URL. You can use the{CHECKOUT_ID}placeholder in the URL to include the checkout session ID in the redirect.returnUrl(optional): An optional relative path or absolute URL which renders a back-button in the Checkout.authenticatedUsersOnly(optional): Whentrue, checkouts are rejected for signed-out and anonymous users. Whenfalse, anonymous checkouts are allowed. Either way, a signed-in user is attached to the checkout as the customer.theme(optional): A string that can be used to enforce the theme of the checkout page. Can be eitherlightordark.
1
Use Checkout Plugin
Update the
use property of the Polar plugin on your BetterAuth server to include the checkout plugin.Checkout Plugin Example
2
Create checkouts using BetterAuth client
When the 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.BetterAuth Checkout with Polar Example
checkout method accepts the following snake_case properties:products(optional): A Polar Product ID or an array of themslug(optional): A string that can be used as a reference to theproductsdefined in the Checkout configreference_id(optional): An identifier saved asreferenceIdin the metadata of the checkout, order & subscription object. See Organization billing.organization_id(optional): Bill a synchronized team customer. See Organization billing.metadata,custom_field_data(optional): Metadata and custom field values for the checkoutallow_discount_codes(optional, defaults totrue),discount_id,discount_code(optional): Discount settings.discount_idtakes precedence when both are supplied.seats,min_seats,max_seats(optional): Seat-based pricing settingsallow_trial,trial_interval,trial_interval_count(optional): Trial settingssuccess_url,return_url(optional): Override the plugin’ssuccessUrlandreturnUrlredirect(optional, defaults totrue): Set tofalseto get the checkouturlback without redirecting
3
Embed the checkout (optional)
Use
checkoutEmbed to open the checkout as an embed on your site instead of redirecting. It accepts the same properties as checkout.BetterAuth Checkout Embed Example
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 theusage plugin in the use property.
Usage Plugin Example
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. BecausecreateCustomerOnSignUp: true sets the Polar customer’s external ID to the BetterAuth user ID, you can pass external_customer_id: session.user.id when ingesting — no extra lookup is needed.
The example below calls the Polar SDK directly from a server route handler, reusing the polarClient you configured in auth.ts:
app/api/ai/video/route.ts (Next.js App Router)
name(string): The name of the event to ingest. For example,ai_usage,video_streamedorfile_uploaded.external_customer_id(string): The BetterAuth user ID. Passsession.user.idfrom 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
Theusage plugin also registers a BetterAuth endpoint that is callable from the client as authClient.usage.ingest(...). It forwards to Polar from the BetterAuth server and attaches the authenticated user automatically. Anonymous users are rejected:
auth-client.ts
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. Themeters 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
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 Thewebhooks plugin can be used to capture incoming events from your Polar organization.
To set up the Polar webhooks plugin on your BetterAuth server, follow the steps below:
1
Configure Webhook Endpoints in Polar
Configure a Webhook endpoint in your Polar Organization Settings page by following this guide. The endpoint is served at
/polar/webhooks under your Better Auth base path, which is /api/auth/polar/webhooks by default.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
3
Use Webhooks Plugin in BetterAuth server
Pass the
webhooks plugin in the use property.Webhooks Plugin Example
webhooks plugin allows you to invoke handlers for all Polar webhook events:
Every handler is an async function that receives the full webhook payload ({ type, timestamp, data }). Fields use the SDK’s snake_case names, for example payload.data.customer_id.
onPayload- Called for every incoming webhook event, in addition to the matching handler belowonCheckoutCreated- Triggered when a checkout is createdonCheckoutExpired- Triggered when a checkout expiresonCheckoutUpdated- Triggered when a checkout is updatedonOrderCreated- Triggered when an order is createdonOrderUpdated- Triggered when an order is updatedonOrderPaid- Triggered when an order is paidonOrderRefunded- Triggered when an order is refundedonRefundCreated- Triggered when a refund is createdonRefundUpdated- Triggered when a refund is updatedonSubscriptionCreated- Triggered when a subscription is createdonSubscriptionUpdated- Triggered when a subscription is updatedonSubscriptionActive- Triggered when a subscription becomes activeonSubscriptionCanceled- Triggered when a subscription is canceledonSubscriptionCycled- Triggered when a subscription enters a new billing periodonSubscriptionPastDue- Triggered when a subscription payment fails and it becomes past dueonSubscriptionPaused- Triggered when a subscription is pausedonSubscriptionResumed- Triggered when a paused subscription is resumedonSubscriptionRevoked- Triggered when a subscription is revokedonSubscriptionUncanceled- Triggered when a subscription cancellation is reversedonProductCreated- Triggered when a product is createdonProductUpdated- Triggered when a product is updatedonOrganizationUpdated- Triggered when an organization is updatedonBenefitCreated- Triggered when a benefit is createdonBenefitUpdated- Triggered when a benefit is updatedonBenefitGrantCreated- Triggered when a benefit grant is createdonBenefitGrantCycled- Triggered when a benefit grant renews with its subscriptiononBenefitGrantUpdated- Triggered when a benefit grant is updatedonBenefitGrantRevoked- Triggered when a benefit grant is revokedonCustomerCreated- Triggered when a customer is createdonCustomerUpdated- Triggered when a customer is updatedonCustomerDeleted- Triggered when a customer is deletedonCustomerStateChanged- Triggered when a customer state changesonCustomerSeatAssigned- Triggered when a seat is assigned to a customeronCustomerSeatClaimed- Triggered when a customer claims an assigned seatonCustomerSeatRevoked- Triggered when a seat is revoked from a customeronDiscountCreated- Triggered when a discount is createdonDiscountUpdated- Triggered when a discount is updatedonDiscountDeleted- Triggered when a discount is deletedonMemberCreated- Triggered when a member is added to a team customeronMemberUpdated- Triggered when a member of a team customer is updatedonMemberDeleted- Triggered when a member is removed from a team customer
webhook-id, webhook-timestamp and webhook-signature headers against your webhook secret before calling any handler:
- A missing or invalid signature returns
403. - A malformed payload returns
400. - A signed event type that the installed SDK doesn’t know yet returns
200and is ignored, so new event types don’t cause retries. - If a handler throws, the error propagates and the request fails, so Polar retries the delivery.
Portal Plugin
Source code A plugin which enables customer management of their purchases, orders and subscriptions.Portal Plugin Example
portal plugin gives the BetterAuth Client a set of customer management methods, scoped under authClient.customer object. To act on a synchronized team customer instead of the user, pass query: { organizationId } to the list and state methods, or fetchOptions: { query: { organizationId } } to portal().
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
{ redirect: false } to get the portal url back without redirecting. Anonymous users are rejected.
2. Customer State
The portal plugin also adds a convenient method to retrieve the Customer State.Retrieve Customer State Example
- All the data about the customer.
- The list of their active subscriptions.
- The list of their granted benefits.
- The list of their active meters, with their current balance.
createCustomerOnSignUp or the request fails.
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. Each returns a paginated list withitems and pagination.
3.1 Benefits
This method only lists granted benefits for the authenticated user/customer.List User Benefits Example
3.2 Orders
This method lists orders like purchases and subscription renewals for the authenticated user/customer.List User Orders Example
3.3 Subscriptions
This method lists the subscriptions associated with authenticated user/customer.List User Subscriptions Example
reference_id or organizationId. See Organization billing. You can’t combine the two.
