← All field notes

Field notes · Databases · 3 min

Supabase row-level security, explained in five minutes

Your Supabase anon key is public by design. Row-level security is what keeps one customer out of another’s data. Here’s how to turn it on and test it.

If your app runs on Supabase, your database is reachable straight from the browser. That’s the point: the app talks to it directly with the anon key, which is public by design and ships in your JavaScript. The only thing standing between that public key and every row in your tables is row-level security, or RLS.

What RLS actually does

RLS is a set of rules attached to each table. Every time someone reads or writes a row, Postgres checks the rules and only lets through the rows they allow. With RLS on, a logged-in customer asking for “all invoices” gets back only their own. With RLS off, they get everyone’s.

Why it gets missed

Turn it on

For every table in the public schema:

alter table invoices enable row level security;

With RLS on and no rules yet, nobody using the public key can read or write anything. That’s the safe starting point. Now add a rule for what should be allowed. For example, “signed-in users can read their own invoices”:

create policy "Owners read their invoices"
on invoices for select
to authenticated
using ( (select auth.uid()) = user_id );

Write separate rules for insert, update and delete. An update rule usually needs both using (which rows you may touch) and with check (what the row may look like afterwards), so nobody can hand their invoice to someone else.

Check it the honest way

  1. In the Supabase dashboard, open Advisors → Security Advisor. It flags tables in exposed schemas that have RLS disabled.
  2. Make two test accounts. Sign in as the first and create some data. Sign in as the second and try to see, change or delete it through every screen and every API route your app has. You should get nothing.
  3. Check storage buckets too. Files have their own policies, separate from tables.

The key that skips all of this

The service_role key ignores RLS completely. It belongs only on your server, in a server route or edge function. If it’s anywhere in your frontend code or a public environment variable, rotate it now. Our note on public .env files shows how to check.

The quick ruleEvery table in public gets RLS on, then one rule per action you actually want to allow. Anything you didn’t write a rule for stays closed.