Polar checkout in Next.js

The free pricing switch sends the chosen plan and billing period to Polar's Next.js checkout route, which redirects the buyer to Polar to pay.

Get it freeOpen the demoReact · Tailwind · TypeScript

Use it in Next.js

app/pricing/page.tsx

"use client";
import PricingSwitch, { type Plan } from "qodesign-free/pricing-switch";

const plans: Plan[] = [
  { id: "starter", name: "Starter", description: "For side projects.", monthly: 9, yearly: 7, cta: "Start Starter", features: ["1 project", "Email support"] },
  { id: "pro", name: "Pro", description: "For a product with paying users.", monthly: 29, yearly: 23, cta: "Start Pro", features: ["10 projects", "Priority support"], featured: true },
];

// One Polar product per plan and billing period, made in your Polar dashboard (sandbox first).
const POLAR_PRODUCTS: Record<string, { monthly: string; yearly: string }> = {
  starter: { monthly: "POLAR_PRODUCT_ID_STARTER_MONTHLY", yearly: "POLAR_PRODUCT_ID_STARTER_YEARLY" },
  pro: { monthly: "POLAR_PRODUCT_ID_PRO_MONTHLY", yearly: "POLAR_PRODUCT_ID_PRO_YEARLY" },
};

export default function PricingPage() {
  return (
    <main className="mx-auto max-w-4xl px-4 py-16">
      <PricingSwitch
        plans={plans}
        header={<h1 className="text-3xl font-semibold">Pricing</h1>}
        yearlyBadge="2 months free"
        onSelect={(plan, yearly) => {
          const ids = POLAR_PRODUCTS[plan.id];
          if (!ids) return;
          window.location.assign(`/checkout?products=${yearly ? ids.yearly : ids.monthly}`);
        }}
      />
    </main>
  );
}

app/checkout/route.ts

import { Checkout } from "@polar-sh/nextjs";

// GET /checkout?products=<id> creates a Polar checkout and redirects the browser to it.
export const GET = Checkout({
  accessToken: process.env.POLAR_ACCESS_TOKEN!,
  successUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/welcome`, // Polar appends checkout_id={CHECKOUT_ID}
  environment: process.env.NODE_ENV === "production" ? "production" : "sandbox",
});

Good to know

  • Tested in a fresh Next.js 16.4.2 app with @polar-sh/nextjs 1.1.0. Start Pro sent the browser to /checkout?products=<the Pro monthly id>, and with Yearly on, Start Starter sent the yearly id. GET /checkout with no products answered 400, as Polar's docs say.
  • We had no Polar account in the test, so the hop into Polar's own checkout page wasn't run. With an invalid access token the route answered 500 and the server log showed Polar's invalid_token error, so if the button lands on an error page, check the token and the environment first.
  • successUrl must be an absolute URL. Polar adds checkout_id={CHECKOUT_ID} to it.
  • The switch shows the numbers in plans; Polar charges what the product says. Change both together.
  • To give access after payment, use the Webhooks handler from the same package (onOrderPaid, onSubscriptionActive).

Install

  1. One commandnpm i qodesign-free framer-motion @polar-sh/nextjs
  2. Or ask your AIPaste PROMPT.md into Cursor, Lovable or v0.

Props

plans
name, monthly, yearly, features, cta
onSelect
(plan, yearly) when a button is pressed
yearlyBadge
text on the switch, e.g. 2 months free
currency
symbol shown

How qodesign compares

All comparisons, with prices read on each library's own site.

Questions

Is the pricing switch free?

Yes. MIT, from the free npm package or the shadcn registry.

Can I pass the buyer's email?

Yes. The checkout route reads customer_email from the query string, so add &customer_email=... to the URL in onSelect.

Can Cursor or v0 add it for me?

Yes. The folder has a PROMPT.md. Paste it into Cursor, Lovable, v0 or Claude Code.

Does the pricing switch work in Next.js?

Yes, as a client component. Keep "use client" at the top of the file that renders it, as in the snippet above. App Router and Pages Router both work.

Polar checkout for Next.js with a monthly/yearly pricing page (free component) · qodesign