# Polar Setup

> Configure Polar as your billing provider

*Canonical: https://makerkit.dev/docs/nextjs-drizzle/billing/polar-setup*

---

Set up Polar by creating an access token, configuring environment variables, creating products in the Polar dashboard, and optionally setting up webhooks. Polar acts as a merchant of record, handling taxes and compliance for you.

This guide is part of the [Billing & Subscriptions](./overview) documentation.

Polar integration uses Better Auth's Polar plugin to handle checkout sessions, customer management, and subscription lifecycle - with Polar acting as the merchant of record for tax and compliance purposes.

## Important: Organization Billing Limitations

Polar does **not** support true per-organization customers the way Stripe does. In Makerkit:

- Organization billing UI can still be shown (via `referenceId = organization.id`)
- But **customer context is user-centric** and organization “customerId” is not available
- The customer portal is effectively **user-level**, not organization-level

If you need strict B2B billing with a dedicated customer per organization, use **Stripe**.

## Prerequisites

- A Polar account (sign up at [polar.sh](https://polar.sh))
- Access to your project's `.env` file

## Step 1: Enable Polar Provider

Set Polar as your billing provider in `.env`:

```bash
NEXT_PUBLIC_BILLING_PROVIDER=polar
```

Also ensure you have:

```bash
# Used for server-side return URLs in billing actions
NEXT_PUBLIC_SITE_URL=http://localhost:3000
```

## Step 2: Get Your Access Token

1. Log in to your Polar Dashboard
2. Navigate to **Settings** → **Developers** → **Personal Access Tokens**
3. Click **Create Token**
4. Give it a name and select appropriate scopes
5. Copy the generated access token

Add to your `.env`:

```bash
POLAR_ACCESS_TOKEN=polar_at_...your_access_token
```

We recommend enabling the following access scopes:
- `benefits:read`
- `checkouts:read`
- `checkout:write`
- `customer_seats:read`
- `customer_portal:read`
- `customer_portal:write`
- `customer_sessions:write`
- `subscriptions:read`
- `subscriptions:write`
- `customers:read`
- `customers:write`
- `meters:read`
- `meters:write`

You may want to add additional access scopes depending on your needs. For example, if you need to read/write orders, you can add the `orders:read` and `orders:write` scopes.

## Step 3: Configure Environment

Set the environment mode:

```bash
# For development/testing
POLAR_ENVIRONMENT=sandbox

# For production
POLAR_ENVIRONMENT=production
```

The default is `sandbox` if not specified.

## Step 4: Create Products

### Using Polar Dashboard

1. Navigate to **Products** in your Polar dashboard
2. Click **Create Product**
3. Configure your product:
  - **Name**: Set the name and description
  - **Pricing**: Choose pricing (subscription or one-time)
  - **Billing Interval**: Set billing interval (monthly/yearly)
  - **Trial Period**: Set the trial period (optional)
  - **Meters**: Optionally, add metered usage:
    - Example: "requests": $0.10 per 1,000 requests
  - **Benefits**: Optionally, add benefits
    - Example: "core-features", "email-support", "priority-support"
4. Copy each Product ID

### Example Product Setup

**Starter Product**
- Monthly: `prod_starter_monthly` ($9.99/month)
- Yearly: `prod_starter_yearly` ($99.99/year)

**Pro Product**
- Monthly: `prod_pro_monthly` ($19.99/month)
- Yearly: `prod_pro_yearly` ($199.99/year)

### Configure Product IDs

Add your Product IDs to `.env`:

```bash
POLAR_PRODUCT_STARTER_MONTHLY=prod_...
POLAR_PRODUCT_STARTER_YEARLY=prod_...
POLAR_PRODUCT_PRO_MONTHLY=prod_...
POLAR_PRODUCT_PRO_YEARLY=prod_...
```

### Free Trial

In Polar, please specify the free trial during the product setup, not the configuration.

## Step 5: Configure Billing Plans

Update your billing configuration to include the details of your products:

```typescript {title="packages/billing/config/src/config.ts"}
import { BillingConfig } from '@kit/billing';

export const billingConfig: BillingConfig = {
  products: [
    {
      id: 'starter',
      name: 'Starter',
      description: 'For individuals and small teams',
      currency: 'USD',
      features: ['Core features', 'Email support'],
      plans: [
        {
          name: 'starter-monthly',
          planId: process.env.POLAR_PRODUCT_STARTER_MONTHLY!,
          displayName: 'Starter Monthly',
          interval: 'month',
          cost: 9.99,
        },
      ],
    },
  ],
};
```

## Step 6: Configure Webhooks

Webhooks are optional. Enable them if you want server-side lifecycle hooks (e.g. logging, analytics, provisioning) to run in response to Polar events.

### Production Webhooks

1. In Polar Dashboard, navigate to **Settings** → **Webhooks**
2. Click **Add Webhook**
3. Set endpoint URL: `https://yourdomain.com/api/auth/polar/webhook`
4. Select events to listen for:
   - `subscription.created`
   - `subscription.updated`
   - `subscription.canceled`
   - `order.paid`
5. Copy the webhook signing secret

Add to your `.env`:

```bash
POLAR_WEBHOOK_SECRET=whsec_...
```

NB; for local development, you can use a tunneling service like ngrok to expose your local server to the internet and receive these events on your local machine.

### Local Development

For local testing, use a tunneling service like [ngrok](https://ngrok.com/) or [Localtunnel](https://github.com/localtunnel/localtunnel).

```bash
# Start ngrok
ngrok http 3000

# Copy the https URL and configure it in Polar webhook settings
# Example: https://abc123.ngrok.io/api/auth/polar/webhook
```

Once started the proxy to your local server will be available at a URL like `https://abc123.ngrok.io`, you must use this URL in the Polar webhook settings.

## Step 7: Test the Integration

### Test Checkout

1. Run the development server: `pnpm dev`
2. Navigate to `/settings/billing`
3. Select a plan and proceed to checkout
4. Complete the checkout flow on Polar

### Verify Webhooks

Monitor your server logs to verify webhook events are being received and processed.

## Environment Variables Reference

Complete `.env` configuration for Polar:

```bash
# Billing Provider
NEXT_PUBLIC_BILLING_PROVIDER=polar

# Polar Configuration
POLAR_ACCESS_TOKEN=polar_at_...
POLAR_ENVIRONMENT=sandbox  # or 'production'
POLAR_WEBHOOK_SECRET=whsec_...

# Optional behavior toggle (default shown)
CREATE_CUSTOMER_ON_SIGN_UP=true

# Product IDs (from Polar Dashboard)
POLAR_PRODUCT_STARTER_MONTHLY=prod_...
POLAR_PRODUCT_STARTER_YEARLY=prod_...
POLAR_PRODUCT_PRO_MONTHLY=prod_...
POLAR_PRODUCT_PRO_YEARLY=prod_...

# Application URL (for redirects)
NEXT_PUBLIC_SITE_URL=http://localhost:3000
```

## Provider Capabilities

Polar supports the following features:

| Feature | Supported | Notes |
|---------|-----------|-------|
| Checkout | Yes | Via Better Auth API |
| Customer Portal | Yes | Customers manage subscriptions here |
| Cancel (API) | No | Use portal for cancellation |
| Restore (API) | No | Use portal for restoration |
| Benefits | Yes | Provider feature; not mapped to `BillingClient` entitlements in Makerkit |
| Usage Meters | Yes | Event-based metering |
| Organizations | Limited | No organization customer context; prefer Stripe for B2B billing |

### Cancel/Restore/Upgrade/Downgrade via Customer Portal

Customers can use the Polar customer portal to manage their subscriptions

### Production Checklist

Before going live:

- [ ] Switch `POLAR_ENVIRONMENT` to `production`
- [ ] Configure production webhook endpoint
- [ ] Test all webhook events in production
- [ ] Verify product IDs are correct and match your billing configuration
- [ ] Set up billing email notifications in Polar

## Troubleshooting

### Webhook Signature Verification Failed

Check that your `POLAR_WEBHOOK_SECRET` matches the webhook in Polar Dashboard.

### Customer Not Created

Ensure the plugin is configured with `createCustomerOnSignUp: true` (default).

### Product ID Not Found

Verify Product IDs exist in Polar Dashboard and match your billing configuration.

### Environment Mismatch

Ensure `POLAR_ENVIRONMENT` matches your Polar account mode (sandbox vs production).

## Lifecycle Hooks

Polar-specific hooks are available in `packages/billing/polar/src/hooks/`:

| Hook | When it's called |
|------|------------------|
| `onSubscriptionCreated` | New subscription created |
| `onSubscriptionUpdated` | Subscription changes |
| `onSubscriptionCanceled` | Subscription canceled |
| `onOrderPaid` | Order payment completed |
| `onCustomerCreated` | Customer record created |
| `onCustomerStateChanged` | Customer state changes |

See [Lifecycle Hooks](./lifecycle-hooks) for implementation details.

## Common Pitfalls

- **Using Price IDs instead of Product IDs**: Polar uses Product IDs (`prod_...`), not Price IDs like Stripe. This is the opposite of Stripe's setup.
- **Expecting organization-level customers**: Polar is user-centric. The `organization.stripeCustomerId` equivalent doesn't exist - customer context is always the user.
- **Setting `freeTrial` in billing config for Polar**: Polar trials are configured in the product setup on their dashboard, not in MakerKit's config.
- **Forgetting to switch POLAR_ENVIRONMENT**: Development uses `sandbox`, production uses `production`. Mismatching environments causes auth failures.
- **Missing webhook configuration**: Without webhooks, subscription state won't update after checkout. Unlike Stripe, there's no local CLI forwarder - use ngrok.
- **Assuming cancel/restore work via API**: Polar doesn't expose cancel/restore APIs. Users must use the customer portal for these operations.

{% faq
   title="Frequently Asked Questions"
   items=[
     {"question": "How do I test Polar without real payments?", "answer": "Set POLAR_ENVIRONMENT=sandbox. The sandbox environment provides test checkout flows without processing real payments."},
     {"question": "Why can't I cancel subscriptions programmatically?", "answer": "Polar doesn't expose cancel/restore APIs. Redirect users to the customer portal via billing.portal() for subscription management."},
     {"question": "Can I use Polar for B2B (organization) billing?", "answer": "The UI works, but Polar has no organization customer concept. For strict B2B billing with per-org customer records, use Stripe instead."},
     {"question": "How do I handle webhook development locally?", "answer": "Use ngrok or similar to expose your local server: ngrok http 3000. Then configure that URL in Polar's webhook settings."},
     {"question": "What happened to Polar benefits?", "answer": "Polar supports benefits, but MakerKit doesn't map them to BillingClient entitlements. Use billing.getProvider().getProviderClient() to access benefits directly."}
   ]
/%}

---

**Next:** [Lifecycle Hooks →](./lifecycle-hooks)
