Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

Stripe Billing Portal in Next.js -- Let Customers Manage Their Own Subscriptions Without Building Billing UI

September 1, 2026
stripenextjssaaspaymentsboilerplate

You wired up Stripe subscriptions. Users can pay. Then the first support email arrives: "How do I update my credit card?" And the second: "I need to cancel. Can you do it for me?" Now you have a support ticket backlog for a problem that should not exist.

The Stripe Customer Portal is Stripe's hosted billing management page. Your customers land on it, manage their own subscription -- update payment method, cancel, download invoices, switch plans -- and return to your app. You redirect them there. Stripe handles everything else.

Here is how long it takes to add: one API endpoint, one button.

What the Stripe Customer Portal handles for you

Out of the box, the portal lets customers:

  • Update their payment method
  • Cancel or pause their subscription
  • Switch between plans you have enabled
  • Download past invoices
  • See their current plan and billing date

All of this runs on Stripe's infrastructure. You configure which options to show from the Stripe dashboard. No custom UI to build, no invoice PDFs to generate, no cancellation flow to wire up.

When to use the portal -- and when to build your own flow

The portal is the right choice when:

  • You want billing management live on day one without custom UI
  • You do not need to intercept the cancel event before it happens
  • Your cancellation path is simple (no pause offer, no exit survey)

Build your own billing management UI when:

  • You want to show a pause offer or discount before customers cancel
  • You need an exit survey to understand why customers leave
  • You want to gate the cancel button behind a "talk to us" step

For most products in the first six months, the portal is the right call. You can always layer a custom cancel flow on top later. The two approaches are not mutually exclusive -- the portal handles payment updates and invoices while your custom flow owns the cancellation path.

See how to reduce SaaS churn with cancel flows and exit surveys if you want to go the custom route instead.

The single endpoint you need

On the server, one Stripe API call creates a portal session and returns a URL:

// app/api/billing/portal/route.ts
import { getUserFromRequest } from "@/lib/auth";
import { handleError } from "@/lib/errors";
import { stripe } from "@/lib/stripe";
import { userService } from "@/modules/user";
 
export async function POST(req: Request) {
  try {
    const user = await getUserFromRequest(req);
    const stripeCustomerId = await userService.getStripeCustomerId(user.id);
 
    const session = await stripe.billingPortal.sessions.create({
      customer: stripeCustomerId,
      return_url: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard`,
    });
 
    return Response.json({ url: session.url });
  } catch (error: unknown) {
    return handleError(error);
  }
}

That is the entire backend. Auth check, Stripe call, URL back. Nothing else.

The button in your dashboard

On the client, a button redirects the customer to the portal:

async function handleBillingPortal() {
  const res = await fetch("/api/billing/portal", {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
  });
  const { url } = await res.json();
  window.location.href = url;
}

Put this in your account or billing settings page. When the customer clicks, they land on Stripe's portal, handle their billing, and return to your app via the return_url when done.

Configure the portal in Stripe first

Before you test, configure the portal in your Stripe dashboard under Settings > Billing > Customer Portal. Decide:

  • Which plans customers can switch to
  • Whether cancellations take effect immediately or at the end of the period
  • Whether to show invoice history and payment method management

You only do this once. The same configuration applies to every customer.

For test mode, use your test API keys. The portal works identically in both environments.

What the boilerplate already gives you

The Next.js SaaS Boilerplate ships with the pieces this endpoint needs:

  • lib/stripe.ts -- a pre-configured Stripe client ready to import
  • getUserFromRequest() in lib/auth.ts -- JWT extraction and validation in one call
  • handleError() in lib/errors/ -- consistent error responses across all routes
  • A userService layer where you add getStripeCustomerId() alongside your other user queries

The route above fits inside the existing API structure without any scaffolding. Add it, configure the portal in Stripe's dashboard, and wire up the button. Billing self-service is live.

The next time a customer needs to update their credit card, they do it themselves. The support ticket never gets written.

Get the boilerplate and have billing management ready before your first customer asks for it: boilerplate.iteam-company.com.