# Polar Setup

> Connect Polar billing to your Next.js Prisma application

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

---

Connect Polar by generating an access token, setting environment variables, creating products in the dashboard, and optionally configuring webhooks. As a merchant of record, Polar handles taxes and compliance.

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

The Polar integration uses Better Auth's Polar plugin for checkout sessions, customer records, and subscription management, with Polar serving as merchant of record for tax and compliance.

## Organization Billing Limitations

Polar lacks true per-organization customers like Stripe. In MakerKit:

- Organization billing UI works (using `referenceId = organization.id`)
- However, **customer context remains user-centric** with no org "customerId"
- The customer portal operates at **user level**, not organization level

For strict B2B billing with dedicated org customers, use **Stripe** instead.

## Before You Start

- A Polar account ([polar.sh](https://polar.sh))
- Access to your `.env` file

## Step 1: Enable Polar

Set Polar as your provider in `.env`:

```bash
NEXT_PUBLIC_BILLING_PROVIDER=polar
```

Also add:

```bash
# Required for server-side return URLs
NEXT_PUBLIC_SITE_URL=http://localhost:3000
```

## Step 2: Create Access Token

1. Sign in to Polar Dashboard
2. Go to **Settings** → **Developers** → **Personal Access Tokens**
3. Click **Create Token**
4. Name it and select required scopes
5. Copy the token

Add to `.env`:

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

Recommended 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`

Add more scopes as needed, like `orders:read` and `orders:write` for order management.

## Step 3: Set Environment Mode

Configure the environment:

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

# For production
POLAR_ENVIRONMENT=production
```

Default is `sandbox` when not set.

## Step 4: Create Products

### Via 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

### Sample Products

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

### Store Product IDs

Add 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_...
```

### Trial Periods

For Polar, configure free trials in the product setup, not the billing config.

## Step 5: Map Plans in Config

Update your billing configuration:

```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: Set Up Webhooks (Optional)

Webhooks enable server-side lifecycle hooks for logging, analytics, and provisioning in response to Polar events.

### Production Setup

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_...
```

For local development, use a tunneling service like ngrok to receive events.

### Local Development

Use [ngrok](https://ngrok.com/) or [Localtunnel](https://github.com/localtunnel/localtunnel) for local testing:

```bash
# Start ngrok
ngrok http 3000

# Use the https URL in Polar webhook settings
# Example: https://abc123.ngrok.io/api/auth/polar/webhook
```

The proxy URL (e.g., `https://abc123.ngrok.io`) must be configured in Polar's webhook settings.

## Step 7: Verify Integration

### Test Checkout Flow

1. Start development: `pnpm dev`
2. Open `/settings/billing`
3. Choose a plan and checkout
4. Complete the flow on Polar

### Confirm Webhooks

Check server logs for incoming webhook events.

## Environment Variables

Full Polar configuration:

```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
```

## Feature Support

Polar capabilities:

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

### Self-Service Portal

Customers manage subscriptions (cancel, restore, upgrade, downgrade) through Polar's customer portal.

### Launch Checklist

Before going live:

- [ ] Set `POLAR_ENVIRONMENT` to `production`
- [ ] Configure production webhook endpoint
- [ ] Verify webhook events in production
- [ ] Confirm product IDs match your config
- [ ] Enable billing notifications in Polar

## Troubleshooting

### Webhook Verification Fails

Verify `POLAR_WEBHOOK_SECRET` matches the Polar Dashboard webhook.

### Customer Not Created

Confirm `createCustomerOnSignUp: true` (the default) is set.

### Product ID Not Found

Check Product IDs exist in Polar Dashboard and match your config.

### Environment Mismatch

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

## Event Hooks

Polar-specific hooks live 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.

## Watch Out For

- **Price IDs instead of Product IDs**: Polar uses Product IDs (`prod_...`), opposite of Stripe's Price IDs.
- **Expecting org-level customers**: Polar is user-centric. No `organization.stripeCustomerId` equivalent exists.
- **`freeTrial` in billing config**: Polar trials are configured in their dashboard, not MakerKit's config.
- **Wrong `POLAR_ENVIRONMENT`**: Development uses `sandbox`, production uses `production`. Mismatch causes auth failures.
- **No webhook setup**: Without webhooks, subscription state stalls after checkout. No local CLI forwarder exists, so use ngrok.
- **Expecting API cancel/restore**: Polar lacks these APIs. Direct users to the customer portal instead.

{% 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.getProviderClient() to access benefits directly."}
   ]
/%}

---

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