Best Boilerplates: Polar.sh + Better Auth 2026
Two authentication and payment libraries have emerged as the indie developer stack of choice in 2026: Better Auth for self-hosted, type-safe authentication, and Polar.sh for developer-first payments with automatic tax handling. Both prioritize owning your infrastructure over vendor lock-in, both have strong TypeScript support, and both are seeing rapid boilerplate adoption.
This guide covers which starters ship both out of the box, how to add either to a boilerplate that doesn't support it yet, and why this stack makes sense for digital product businesses.
This is a different angle than our related articles: The Polar.sh payments boilerplate guide focuses on Polar.sh payment options. The Better Auth boilerplate roundup covers auth-only adoption. This article covers the specific combination — boilerplates that have integrated both, and why the pairing is particularly good for solo devs and small teams.
Why Polar.sh + Better Auth Is a Compelling Stack
Polar.sh: Payments Without the Compliance Headache
Polar.sh charges 4% + $0.40 per transaction — higher than Stripe's 2.9% + $0.30. The fee difference is the cost of Polar acting as the Merchant of Record: Polar collects and remits VAT, GST, and sales tax in 40+ countries on your behalf. You receive net payouts. You never file a VAT return for a European customer's $19 purchase.
For a solo developer selling a SaaS product globally, the Stripe alternative is: pay 2.9% + use Stripe Tax (add 0.5%) + manually register for VAT in countries where you cross thresholds + file periodic VAT returns. The true cost of Stripe for global digital products is often higher than Polar's 4%.
Polar also ships:
- GitHub Sponsors integration for open-source funding tiers
- Automatic license key delivery for software products
- Benefit grants (grant GitHub repo access, Discord roles, or file downloads on purchase)
- Usage-based billing
- A clean webhook system for subscription lifecycle events
Better Auth: Self-Hosted Auth with Modern Features
Better Auth launched in 2024 and has become the default auth recommendation for new projects that don't want Clerk's per-MAU pricing. It's fully TypeScript-native, ships with a plugin system, and supports:
- Email/password with email verification
- OAuth (Google, GitHub, Apple, and 20+ providers)
- Passkeys (WebAuthn)
- Magic links
- RBAC with roles and permissions
- Multi-tenancy (organizations with member roles)
- Session management with device tracking
- Two-factor authentication
The killer feature is the Better Auth client: it generates a type-safe client from your server auth configuration, so your frontend gets autocompletion for every auth method.
Why Polar + Better Auth Together?
There's actually a first-class integration: @polar-sh/better-auth is a Better Auth plugin that lets you gate resources by Polar subscription status directly in your auth session. A user's subscription tier becomes part of their session data — no separate API call to check if they're a paying customer.
// server/auth.ts — Polar integrated as a Better Auth plugin
import { betterAuth } from "better-auth";
import { polar } from "@polar-sh/better-auth";
import { Polar } from "@polar-sh/sdk";
const client = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN!,
});
export const auth = betterAuth({
database: /* your db adapter */,
plugins: [
polar({
client,
createCustomerOnSignUp: true,
onCustomerCreate: async ({ customer, user }) => {
// Called when a new Polar customer is created for a new user
console.log(`Customer created: ${customer.id} for user ${user.id}`);
},
checkout: {
enabled: true,
products: [
{
productId: process.env.POLAR_PRO_PRODUCT_ID!,
slug: "pro",
},
],
successUrl: "/dashboard?upgrade=success",
},
webhooks: {
secret: process.env.POLAR_WEBHOOK_SECRET!,
onPayload: async ({ event }) => {
// Handle all Polar webhook events here
if (event.type === "subscription.active") {
// Update user's plan in your database
}
},
},
}),
],
});
Boilerplates That Ship Both
1. Supastarter (Next.js)
Supastarter added a Polar.sh variant alongside its default Stripe config. The Better Auth integration in Supastarter is mature — it was one of the first commercial boilerplates to adopt Better Auth and ship with pre-built auth UI components for sign-up, sign-in, password reset, and organization management.
The Polar + Better Auth combination in Supastarter uses the @polar-sh/better-auth plugin, so subscription status flows into the auth session automatically.
Price: $299+ Stack: Next.js, Supabase/Postgres, Better Auth, Polar.sh or Stripe, Resend Notable: Multi-tenancy with Better Auth organizations, full auth UI components pre-built Website: supastarter.dev
2. Next.js Starter Kit (michaelshimeles)
This open-source starter ships Better Auth and Polar.sh as its default auth and billing stack. It's lightweight — no multi-tenancy, no admin panel — but it's a working reference implementation of the Polar + Better Auth combination that you can inspect and adapt.
Price: Free (MIT) Stack: Next.js, Drizzle, Better Auth, Polar.sh Best for: Developers who want a minimal reference implementation to understand the integration GitHub: michaelshimeles/nextjs-starter-kit
3. Tempo (Next.js + Supabase + Polar)
Tempo is a boilerplate built around Next.js, Supabase, and Polar.sh. It uses Supabase Auth rather than Better Auth, but if you're already in the Supabase ecosystem, it's worth noting as it ships with Polar.sh fully integrated for subscriptions.
Price: Free Stack: Next.js, Supabase, Polar.sh Note: Uses Supabase Auth, not Better Auth Docs: tempolabsinc.mintlify.app
Adding Polar.sh to Any Boilerplate
If your current boilerplate uses Stripe and you want to add Polar.sh (or offer it as an alternative):
Step 1: Install the SDK
npm install @polar-sh/sdk @polar-sh/nextjs
Step 2: Configure the Client
// lib/polar.ts
import { Polar } from "@polar-sh/sdk";
export const polar = new Polar({
accessToken: process.env.POLAR_ACCESS_TOKEN!,
server: process.env.NODE_ENV === "production" ? "production" : "sandbox",
});
Step 3: Create a Checkout Route
// app/api/checkout/route.ts
import { NextRequest } from "next/server";
import { polar } from "@/lib/polar";
import { auth } from "@/lib/auth";
export async function GET(request: NextRequest) {
const session = await auth.api.getSession({ headers: request.headers });
if (!session?.user) {
return new Response("Unauthorized", { status: 401 });
}
const { searchParams } = new URL(request.url);
const productId = searchParams.get("productId");
if (!productId) {
return new Response("Missing productId", { status: 400 });
}
const checkout = await polar.checkouts.create({
productId,
customerEmail: session.user.email,
successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
metadata: {
userId: session.user.id,
},
});
return Response.redirect(checkout.url);
}
Step 4: Handle Webhooks
// app/api/webhooks/polar/route.ts
import { validateEvent, WebhookVerificationError } from "@polar-sh/sdk/webhooks";
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get("webhook-signature") ?? "";
let event;
try {
event = validateEvent(body, { "webhook-signature": signature }, process.env.POLAR_WEBHOOK_SECRET!);
} catch (error) {
if (error instanceof WebhookVerificationError) {
return new Response("Invalid signature", { status: 403 });
}
throw error;
}
switch (event.type) {
case "subscription.active": {
const subscription = event.data;
// Grant access: update user's plan in your database
await db.user.update({
where: { polarCustomerId: subscription.customerId },
data: {
plan: "pro",
polarSubscriptionId: subscription.id,
subscriptionStatus: "active",
},
});
break;
}
case "subscription.canceled": {
const subscription = event.data;
await db.user.update({
where: { polarCustomerId: subscription.customerId },
data: { plan: "free", subscriptionStatus: "canceled" },
});
break;
}
case "order.created": {
// One-time purchase — deliver the product
const order = event.data;
await deliverProduct(order);
break;
}
}
return new Response("OK");
}
Adding Better Auth to Any Boilerplate
If your boilerplate uses NextAuth v4 or Lucia and you want to migrate to Better Auth:
Step 1: Install
npm install better-auth
Step 2: Configure the Server
// server/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { multiSession, organization, twoFactor } from "better-auth/plugins";
import { db } from "@/db";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql" or "sqlite"
}),
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
plugins: [
organization(), // Multi-tenancy with member roles
twoFactor(), // TOTP 2FA
],
trustedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
});
Step 3: Create the API Route
// app/api/auth/[...all]/route.ts
import { auth } from "@/server/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
Step 4: Generate the Type-Safe Client
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { organizationClient, twoFactorClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_APP_URL!,
plugins: [organizationClient(), twoFactorClient()],
});
// Export typed hooks and methods
export const { signIn, signUp, signOut, useSession } = authClient;
Polar.sh vs Stripe: The Right Choice For Your Boilerplate
| Use Polar.sh if | Use Stripe if |
|---|---|
| You sell digital products globally and hate tax compliance | You need complex subscription billing (metered, per-seat, trials) |
| You want automatic Merchant of Record for VAT/GST | You're a US-only product and tax compliance is manageable |
| You're building for the developer/indie hacker market | You need Stripe's Connect for marketplace payouts |
| You want GitHub Sponsors integration | You have existing Stripe integrations to maintain |
| License key delivery is part of your product | You need the full breadth of Stripe's financial products |
Neither is universally better. The fee difference (4% vs 2.9%) reverses when you account for Stripe Tax and VAT registration costs. Polar is genuinely simpler for indie developers selling to a global audience.
Summary
The Polar.sh + Better Auth stack is optimized for indie developers and small teams who want to own their auth data, avoid per-MAU fees, and sell globally without becoming a tax expert. The @polar-sh/better-auth plugin makes the integration tight — subscription status flows directly into the auth session.
Supastarter is the best full-featured commercial option shipping both. For a free reference implementation, the michaelshimeles starter kit is worth examining. For any other boilerplate, the code in this guide gets you both integrations running in a few hours.
Migrating from Clerk + Stripe to Better Auth + Polar.sh
If your current boilerplate uses Clerk and Stripe and you want to migrate, the process has two independent parts you can do separately:
Migrate auth first (lower risk). Better Auth provides a migration guide and compatible session format. The main work is replacing auth() callsites and updating environment variables. Your billing and database code doesn't change. This typically takes 1–2 days.
Migrate billing separately. Polar.sh and Stripe can coexist — you can offer Polar.sh for new customers while existing Stripe subscribers stay on Stripe until their subscriptions naturally end. This dual-billing approach avoids forcing migrations on paying customers.
Test the Polar.sh webhook handler thoroughly before going live. Unlike Stripe's test mode (which has excellent event simulation), Polar's sandbox environment is less mature. Use their sandbox API keys and manually trigger test purchases to verify your subscription.active and order.created webhook handlers work correctly.
For a complete comparison of authentication options, see the Better Auth vs Clerk vs NextAuth comparison and our Stripe vs Polar vs Lemon Squeezy breakdown.