⭐🚪💼
If you have been living under a rock, the rule for multi-product Supabase setups is "one project, many products". Aidxn site, MIDI Bridge, Velocity 9 admin — all three sit on the same Supabase project and share the same auth.users table. You do not fork the schema per product. You do not stand up a second Supabase. You extend the existing profiles table and let RLS do the gating.
Spoiler: adding a Pro tier is a single ALTER TABLE. The boolean lives on profiles because every RLS policy in the project already joins through profiles.id = auth.uid(). New tables would mean new joins, new policies, new mistakes. Extend, do not bolt on.
The component-level gate — "show this button only if user.isPro" — is UX, not security. A motivated user opens devtools, flips the React state, and the button is back. Do not pretend client checks are a boundary. The database is the boundary. Everything that matters goes through a view that filters at the row level.
Plot twist: SECURITY INVOKER (not DEFINER) is the right default. INVOKER runs the view as the calling user, so RLS on the underlying tables still fires. DEFINER bypasses RLS by running as the view owner, which sounds convenient until you accidentally leak admin-only data to a free user. The Supabase docs flag this and you should listen. Yes really.
The catch: is_pro as a boolean works for one binary gate. The moment you have "Starter has 10 dashboards, Pro has unlimited, Enterprise has multi-org" you have outgrown the boolean. That is when you graduate to v9_plans plus v9_tenant_subscriptions — a real billing ladder with per-tier quotas. Separate post, separate complexity budget, separate Stripe webhook. Do not pre-build it.
The verdict: behold, one column, one view, one RLS policy, three products sharing the same auth and the same Pro tier. The database stays the source of truth, the components stay dumb, and the next product on the same Supabase project inherits the gating for free. Do not @ me about Clerk — you do not need it.