# Build a Membership Site in Lovable

- Tool: Lovable Prompts
- Last updated: June 2026

## TL;DR

Paste the starter prompt below into Lovable Build mode and you get a tier-gated membership site scaffold — free, premium, and founder tiers, a Stripe-powered pricing page, content library with RLS-enforced access, and a Stripe customer portal. Follow five prompts in sequence. Expected build: 1–2 days, ~150–250 credits on a Pro $25/mo plan.

## Frequently asked questions

### Will premium content actually be secure, or can savvy users get it via DevTools?

Yes, it is secure — if you follow follow-up #1. The starter prompt creates an RLS policy on the content table that restricts which rows Supabase returns based on the user's tier from their JWT. A free user's Supabase query literally returns zero premium rows — the body_md field never reaches the browser. The React Paywall component is just a UX signal on top of a real database-level gate. If you skip follow-up #1 and only gate in React code, that is not secure and any user with DevTools can read the full article. The test: curl the Supabase REST endpoint /rest/v1/content with the anon key — if you see body_md for min_tier='premium' rows, your RLS is missing.

### Why is tier stored in app_metadata instead of profiles.tier?

JWT app_metadata is server-controlled — only the Supabase service-role key (used by your Edge Function webhook) can write to it. User metadata and the profiles table are client-accessible, which means a determined user could potentially modify their own tier value with a direct Supabase client call. By storing the authoritative tier in app_metadata and reading it in RLS via auth.jwt() -> 'app_metadata' ->> 'tier', you guarantee that no client-side code can escalate their own tier. The profiles.tier column is a mirror — kept in sync by the webhook — so you can run Postgres queries against it, but RLS reads from the JWT, not from profiles.

### How do I migrate existing Memberstack or Substack subscribers?

For Memberstack, export your subscriber list as CSV (Members → Export). Create Stripe customers for each subscriber via the Stripe API or Dashboard bulk import. Set their tiers manually via the Supabase Dashboard: Cloud tab → Users & Auth → find the user → edit app_metadata to add their tier. For Substack, export your paid subscriber list from Settings → Exports. The migration steps are: (1) get users to re-register on your new site, (2) import them into Stripe as existing customers with their subscription history, (3) set their app_metadata.tier via the Supabase admin API. There is no automated one-click migration tool — plan 2–4 hours for up to 500 subscribers.

### Can I offer a free trial that auto-converts to paid?

Yes, through Stripe's built-in trial support. In the create-checkout-session Edge Function, add subscription_data: {trial_period_days: 14} to the session parameters. Stripe will not charge the card until the trial ends. Your webhook already handles customer.subscription.trial_will_end (fires 3 days before end) — wire that event to the Resend 'trial_ending' email in follow-up #4. The user's tier is set to 'premium' on checkout.session.completed (when they enter their card), so they get full access immediately. If they cancel before trial ends, customer.subscription.deleted fires and the webhook downgrades them to 'free'.

### What happens if Stripe's webhook fails — do users lose access?

If the webhook fails (network error, Edge Function crash, or Stripe is down), Stripe retries delivery for up to 72 hours with exponential backoff. During that window, the user's tier in your DB may lag behind their Stripe subscription state. The stripe_events idempotency table prevents duplicate processing if a retry comes in after the first delivery eventually succeeds. For a safety net, add the sync-subscription-from-stripe Edge Function (a utility that calls stripe.subscriptions.retrieve and force-updates the DB) and wire a call to it from the /account page on load, as a fallback for the rare case where webhook and DB get out of sync.

### How do I handle prorated upgrades from premium to founder?

Call stripe.subscriptions.update(subscriptionId, {items: [{id: existingItemId, price: founderPriceId}], proration_behavior: 'create_prorations'}) in a new upgrade-plan Edge Function. Stripe immediately issues a prorated invoice for the price difference. Your webhook handles customer.subscription.updated with the new plan and updates app_metadata.tier to 'founder'. Add an 'Upgrade to Founder' button on the /account page that calls this Edge Function and refreshes the session afterward.

### When does it make sense to build this vs stay on Memberstack?

Stay on Memberstack Basic ($25/mo + 4% transaction fee) if you have fewer than 100 subscribers and want to validate the audience first — at 50 subscribers at $20/mo each, you pay about $65 in combined fees, which is cheaper than the Lovable credits and time to build. Build in Lovable when you have 200+ subscribers, the 4% Memberstack transaction fee starts to visibly drain MRR, or you need a custom content model (mixed video + article + audio + community) that Memberstack cannot represent. At 500 subscribers at $20/mo = $10,000 MRR, Memberstack Basic takes $425/mo in fees vs. Stripe's $320/mo through your own app — a $105/mo saving that pays back the build cost in under a year.

### Can RapidDev build this membership site for me end-to-end?

If your build outgrows this prompt kit and you need custom architecture, RapidDev builds production-grade Lovable apps at $13K-$25K — book a free 30-minute consultation at rapidevelopers.com.

---

Source: https://www.rapidevelopers.com/lovable-prompts/lovable-prompts-for-building-membership-site
© RapidDev — https://www.rapidevelopers.com/lovable-prompts/lovable-prompts-for-building-membership-site
