Verified against Lovable · 2026-07-25
Build a membership community with subscription-tier content gating
A Lovable brief for a membership community where tier-gating happens at the query layer so a free member's own API response never contains a paid post's real content, with an explicit grace period for failed payments.
The prompt
Ready to copy — highlighted parts are example details you can swap.
Build Fretwork Collective, a membership community app where guitar technique lessons content is gated by subscription tier — free members should never be able to fetch the actual content of a paid post, not just see it blurred by CSS. PRODUCT Members join for free and can upgrade to paid tiers to unlock more content and community access. Tiers, low to high: Free, Member ($12/mo), VIP ($35/mo). DATA MODEL - Member: id, user_id, display_name, current_tier, stripe_subscription_id, subscription_status (active, past_due, or canceled). - ContentItem: id, title, body_or_video_url, min_tier_required, author_id, published_at. Sample content types: video lessons and downloadable PDF tab sheets. - Post: id, author_id, body, created_at — community discussion posts, visible to all Members regardless of tier. - Comment: id, post_id (references Post), author_id, body, created_at. - TierChangeLog: id, member_id, old_tier, new_tier, changed_at, reason (upgrade, downgrade, or payment_failed) — for support visibility into who changed tier and why. GATING RULES Gating happens at the data layer, not the presentation layer. Write the query or row-level security policy for ContentItem so that a Member whose current_tier is below a piece of content's min_tier_required never receives that content's body_or_video_url in the response at all — send back the title, a locked flag, and nothing else. A blurred-overlay-on-top-of-the-real-content approach is explicitly wrong here since the real content would still be sitting in the page source or the network response for anyone to read. When subscription_status moves to past_due, treat the Member as their free tier for gating purposes immediately, but do not delete or downgrade their current_tier value until either payment recovers or a grace period of 5 days elapses — log every transition to TierChangeLog. When a Member downgrades or their payment fails permanently, any comments or posts they already made stay visible to the community exactly as before; only forward-looking content access changes, never their past contributions. SCREENS 1. Content feed: mixed list of Posts (always visible) and ContentItems (visible in full to eligible tiers, shown as a locked card with a one-line teaser and an "Upgrade to unlock" CTA to everyone else). 2. Individual ContentItem page — if the viewer is below min_tier_required, redirect or render only the locked state, never the real body. 3. Billing/upgrade page listing Free, Member ($12/mo), VIP ($35/mo) with a Stripe customer-portal link for tier changes. 4. Admin content editor: set min_tier_required per ContentItem on publish, with a live preview of exactly what a free member versus a paid member would each see. 5. Community discussion view for Posts and Comments, unrestricted by tier. CONSTRAINTS Use Lovable's Supabase integration for auth, database, and RLS; route tier changes through Stripe's customer portal rather than a custom billing UI. Warm, slightly rustic palette — this is a hobbyist community, not a corporate LMS. Before calling this done, confirm — in plain language — what a free-tier member's raw API response for a VIP-only ContentItem actually contains, not just what the rendered page shows them.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
An AI builder given "gate content by tier" with no further instruction very often implements gating as a frontend conditional that blurs or hides a div while still rendering the real body text or video URL into the page's initial HTML or a fetched JSON payload — trivially visible via view-source or the network tab regardless of what the screen shows. Explicitly demanding that the ineligible field itself must not be present in the response, not merely styled as hidden, forces server- or RLS-level filtering: the query for a locked ContentItem has to actually omit body_or_video_url from what it selects for that member, not just from what the component renders. Naming a past_due grace period rather than instant downgrade addresses a real Stripe subscription lifecycle detail: subscriptions routinely enter past_due before ultimately failing — an expired card, a bank declining a single charge that clears on retry — and yanking access on the very first missed payment is a well-documented driver of unnecessary paid churn. A model given no explicit rule about this treats "not currently active" as "instantly free tier," which is the easy default; stating a grace period explicitly, with its own logged transition reason, stops that default from shipping unexamined. Preserving past contributions on downgrade closes a specific and easy-to-miss failure mode for a community product: retroactively hiding a comment a now-downgraded member made while they were paid breaks conversation threads other members were following and reads as punitive rather than as a billing consequence. A model with no explicit scope for its access-control logic will tend to apply it uniformly to everything the member ever touched — because that's the simplest implementation — rather than scoping it specifically to future content access, which is the only thing a subscription tier actually governs. Stating this distinction is what keeps the gating rule from quietly overreaching into content it was never meant to control.
What you get back
A free-tier member's fetched response for a VIP-only lesson contains only { title, locked: true } — no body_or_video_url field at all — while the same request from a VIP member returns the full content, confirmed by comparing the two raw network responses side by side rather than just the two rendered screens.
Verified against
Lovable Lovable web app (Supabase-integrated chat builder) · 2026-07-25
Changelog
- 2026-07-25 — Initial publish, verified against Lovable with query-level content gating and an explicit past_due grace period.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
