How to set up Stripe subscriptions in a Nuxt + Supabase app

The five decisions behind Stripe subscriptions on a Nuxt + Supabase stack: plans in config, checkout, webhooks, RLS-enforced access, and the customer portal.

June 9, 2026

Subscriptions are the part of a SaaS where the demo and the reality diverge most. The happy path, where a user clicks subscribe, pays, and gets access, is maybe a fifth of the work. The other four fifths is everything that happens after, and Stripe plus Supabase have a clean division of labor for it if you set things up in the right order.

Here is the order that has worked for me on a Nuxt + Supabase app.

1. Define your plans in one place

Before any Stripe code, decide what your plans are and give each a slug your app understands, like free, pro, team. Keep the slugs in your codebase and keep the Stripe price IDs in an environment map next to them. The reason to separate the two: your app should reason about pro, not about price_1Q2x..., and you will have different price IDs in test and live mode anyway. One source of truth for the slugs, one env map for the IDs.

2. Checkout is a redirect, nothing more

Create a Stripe Checkout session on the server, hand the URL to the browser, redirect. Do not record anything about the subscription at this point. The checkout session succeeding tells you the user started paying, not that they are entitled to anything yet. Resist the urge to flip a flag on the success page. That instinct is the single most common billing bug, and here is why.

3. The webhook is where subscriptions actually live

This is the heart of it. Stripe sends events for the whole lifecycle: subscription created, renewed, payment failed, cancelled, refunded. Your webhook handler, signature-verified and idempotent, is the only thing that writes subscription state to Supabase. When customer.subscription.updated says the plan changed, you update the row. When it says the payment is past due, you reflect that. The database is a mirror of what Stripe believes, never a guess your frontend made.

4. Turn subscription state into access

Now you have a row in Supabase that says this user is on pro and the subscription is active. Access control reads from that row. The important part, again, is where you enforce it: in your row-level security policies, so a pro-only table is genuinely unreachable for a free user, not just visually hidden. The UI can hide buttons for nice UX, but the policy is what makes the limit real.

5. Give them the customer portal

You do not want to build cancellation, card updates, and invoice history yourself. Stripe's customer portal does all of it. Generate a portal session on the server, redirect the user, and let your webhook pick up whatever they change there. One less set of screens to build and keep correct.

The shape of it

Plans in config. Checkout as a dumb redirect. Webhook as the only writer of state. RLS as the enforcer. Portal for self-service. Get those five right and subscriptions stop being scary, because every piece has exactly one job.

If you would rather not assemble all five by hand, this is the billing setup BoiledPlate ships by default on a Nuxt 4 + Supabase stack. Subscriptions, one-time, or none, chosen when the starter sets itself up, with the webhook, the RLS limits and the portal already wired. Think of it as the version of this post where someone already made the five decisions, so you just build your product on top.

#stripe #subscriptions #nuxt #supabase #saas

Read more