Row Level Security is the most powerful and most misunderstood feature in Supabase. Every Next.js + Supabase developer gets burned by it at least once. Here are the exact patterns I use across all 6 of my production apps.
What RLS Actually Does
RLS adds a WHERE clause to every database query automatically, based on the authenticated user. Without it, any authenticated user can read any row in your database even other users' data. With it, users can only see what you explicitly allow.
The Basic Pattern
-- Enable RLS
ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
-- Users can only read their own rows
CREATE POLICY "users_own_data" ON subscriptions
FOR ALL USING (auth.uid() = user_id);
-- Service role bypasses RLS (for webhooks)
-- Use service role key in webhook handlers onlyThe Shared Resource Pattern
When users need to share data (like a shared board or team workspace), RLS gets more complex. You need a join table that maps users to resources, and your policy queries that table.
The Webhook Gotcha
Stripe webhooks run server-side with no authenticated user. If you use the anon key in your webhook handler, RLS will block all writes. Use the service role key in webhook handlers but only there, never client-side.
Need a secure Supabase architecture? I've built it 6 times.