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.
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.
Read more
ShipFast vs BoiledPlate: SaaS Boilerplate Comparison
Compare ShipFast and BoiledPlate SaaS templates. Learn which boilerplate handles auth, Stripe, and infrastructure best for your Next.js project.
ShipFast Reviews: Honest Comparison vs BoiledPlate
ShipFast reviews show strong UI, but we compare the real plumbing: webhooks, refunds, row-level security. See where each wins for your launch.
ShipFast Repo Free: Real Comparison vs Paid Alternatives
Compare free ShipFast repos with paid versions. Discover what's included, what's missing, and whether free boilerplates actually save you time.

BoiledPlate