A deep-dive into Supabase RLS policies for SaaS products — covering per-org isolation, admin bypass patterns, and testing strategies.
Row Level Security is Supabase's superpower for multi-tenant applications. Done right, it means you literally cannot accidentally serve one tenant's data to another — the database enforces the boundary, not your application code.
Every table that contains tenant-scoped data gets an org_id column and a policy:
create policy "Users see only their org data"
on public.items
for select
to authenticated
using (
org_id = (auth.jwt() ->> 'org_id')::uuid
);
The auth.jwt() function returns the current user's JWT payload. You can embed any claim you need — including org_id — using Supabase Auth hooks.
For an admin dashboard that needs to see all data, use the service role key server-side (never expose it to the client) and it bypasses RLS entirely:
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY // bypasses RLS
);
Always test policies by impersonating different roles in the SQL editor:
set role anon;
select * from posts; -- should only see published rows
reset role;