Row Level Security in Supabase, Explained for Non-Experts
Row Level Security sounds like a database-admin topic, but for Supabase it's the whole ballgame. It's the layer that decides, on every single query, which rows a given user is allowed to see or change. Skip it and your public key can touch everything. This is the gentle, example-first introduction.
What RLS actually does
Normally a database trusts whoever connects. RLS flips that: every query is filtered by policies you write, based on who's asking. A policy is just a rule like *"a user can read a row only if its `user_id` matches their own id."* Postgres enforces it automatically — your app code can't forget to.
Step 1: turn it on
RLS is per-table and off by default on tables you create via SQL. Enable it: `alter table public.notes enable row level security;`. The moment you do, all access through the public key is denied until you add a policy. That locked-down state is the safe default to build from.
Step 2: write the everyday policies
The most common pattern — each user owns their rows — looks like this for reads: a `select` policy `using (auth.uid() = user_id)`. For inserts you use a `with check (auth.uid() = user_id)` clause so users can only create rows owned by themselves. You write one policy per operation (select, insert, update, delete) per role.
`auth.uid()` returns the id of the logged-in user making the request, which is how Supabase ties a row to its owner.
Common mistakes to avoid
`using (true)` for everyone. That re-opens the table to the public — it's the same as having no protection. Only use it for genuinely public, read-only data.
Forgetting `with check` on writes. A read policy doesn't restrict inserts; without a check clause a user can write rows they shouldn't own.
Testing only while logged in as yourself. Always test with a second account (or the raw anon key) to confirm you can't see other people's data. Our exposure checklist shows how.
The service_role exception
Your backend sometimes needs to bypass RLS — for admin jobs or trusted server code. That's what the service_role key is for. It ignores all policies, so it must stay server-side only, exactly like any other secret. Never put it in the browser.