Guide

Supabase row-level security, explained for non-developers

Published · 6 min read

If your app was built with an AI tool and it has logins, saved data or a waitlist, there is a good chance it runs on Supabase. Supabase gives your app a Postgres database that the browser can talk to directly. That is why one setting, row-level security (RLS), decides whether your users’ data is private or readable by anyone who knows where to look.

You don’t need to be a developer to understand it, or to check it. This guide covers what RLS is, what the warnings mean, how to check your own project, and the one key that must never go near your website’s code.

The key in your site’s code is meant to be there

Open any Supabase-backed site’s JavaScript and you will find a project URL and a key. That key is not, by itself, a problem.

Supabase has two kinds of key, and it has been renaming them:

Key Older name Where it belongs
Publishable key (sb_publishable_...) anon key In your website or app. Public by design.
Secret key (sb_secret_...) service_role key Only on a server you control. Never public.

Supabase introduced the publishable and secret keys in 2025, with an early preview in June and a full launch planned for July, according to its published timeline. The API keys guide says the old and new systems work side by side, and that Supabase is deprecating the anon and service_role keys by the end of 2026. So depending on when your project was made, you may see either set, or both.

The important line in Supabase’s docs is about the publishable key: “Anyone can read it, so it only reaches what Row Level Security allows.” In other words, the key is not the lock. RLS is the lock. The key just gets you to the front door.

What row-level security actually does

Think of your database as a filing cabinet on the pavement outside your shop. The publishable key lets anyone walk up to it. RLS is the rules on each drawer saying who may open it.

Technically, RLS is a Postgres feature. You switch it on per table, then write policies. Supabase describes a policy as working like a hidden WHERE clause added to every query, so the database only returns rows that pass the test. The PostgreSQL documentation adds one useful detail: if RLS is on and no policy exists, the default is to deny everything. No rows are visible and none can be changed.

That gives you three possible states for any table:

  • RLS disabled. No rules at all. Supabase’s Security Advisor describes this as “Table publicly accessible” and warns that “Anyone with your project URL can read, edit, and delete all data in this table.”
  • RLS enabled, no policies. Locked shut. Nobody can read or write through the public API. Safe, but your app may stop working until you add policies.
  • RLS enabled, with policies. The normal, healthy state. Each policy says exactly who can do what.

“A policy that says true”

There is a fourth state that looks fine at a glance and isn’t. A policy is a yes-or-no test. If the test is simply true, the answer is always yes.

create policy "Anyone can read"
on profiles for select
to anon
using ( true );

RLS is technically on. The dashboard shows a policy. But anyone, without even signing in, can read every row. The Supabase RLS guide says to use this only for data that is meant to be public, such as published blog posts or a product list. For anything personal, it is the same as having no lock. Supabase’s linter has a specific check for always-true policies for this reason.

Patch has seen a lot of these. They are usually labelled “temporary”.

How to check your project in the dashboard

  1. Run the Security Advisor. In your Supabase project, open the Advisors section and choose Security Advisor. The Advisors docs list what it flags, including tables in the public schema with RLS turned off (an error), policies that exist but are not enforced because RLS is off, tables with RLS on but no policies, and views that bypass RLS.
  2. Look at your tables and policies. Supabase’s production checklist points you to the Database section of the dashboard to enable RLS and create policies. Go through each table that holds anything about a person: profiles, orders, messages, form submissions.
  3. Double-check with one query. If you are comfortable pasting into the SQL Editor, the Tables guide gives this query. Every row should say true.
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by tablename;

One more thing worth knowing: the Tables guide says the Table Editor switches RLS on for you when you create a table in the dashboard, but tables created with SQL need it enabled by hand. If an AI tool or a migration script set up your tables by running SQL, check each one. That is how an otherwise careful project ends up with one open table.

An example policy: “users can read their own rows”

The most common rule you want is “a signed-in person can see their own data and nobody else’s”. Supabase provides a helper, auth.uid(), which returns the ID of the user making the request. Here is the pattern from the Supabase docs, for a profiles table with a user_id column:

alter table public.profiles enable row level security;

create policy "Users can view their own profile."
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );

create policy "Users can create their own profile."
on profiles for insert
to authenticated
with check ( (select auth.uid()) = user_id );

In plain English: switch the lock on; let signed-in users read a row only when the row’s user_id matches their own ID; let them add a row only if it is stamped with their own ID. Wrapping auth.uid() in select is a performance tip from the Supabase docs, not a typo. Supabase also notes that auth.uid() returns nothing for visitors who aren’t signed in, so these policies don’t match anonymous visitors.

If an AI tool wrote your policies, ask it to explain each one in a sentence. If the sentence is “everyone can read everything” and the table holds personal data, that is your answer.

Never put the secret key in your frontend

The secret key (or legacy service_role key) is the master key. Supabase’s API keys guide is blunt: “A secret key bypasses every Row Level Security policy you have,” and you should never put one “in a browser, a shipped application, or source control.” Postgres itself lets roles with the BYPASSRLS attribute skip all policies, and that is how this key works.

The newer secret keys go a step further: Supabase says they don’t work from a browser at all, and requests that look like they come from one get an “Unauthorized” error. Don’t treat that as a safety net. Whichever version you have, if it is in your site’s JavaScript, anyone can copy it and use it to read, change or delete everything. If you find it there, treat it as leaked: create a new secret key in Settings > API Keys, disable the old one there, and keep the new one on a server or in an edge function.

ShipPatrol’s exposed keys check looks for exactly this in your public code.

Why this matters for personal data

If your tables hold names, emails or anything else about people, UK GDPR expects you to keep that data secure. The ICO’s guide to data security describes the principle as processing personal data securely using “appropriate technical and organisational measures”. A table anyone can download is hard to describe that way. This is general information, not legal advice.

How ShipPatrol checks this

Our Supabase RLS check works differently from our other checks. Spotting a public key in your page is something anyone can do. Testing whether that key can actually read your tables is not something we do to strangers’ sites. So ShipPatrol only checks database access after you verify you own the site. Then it uses the public key your own site already ships to ask each table it can find for a single row, as a signed-out visitor would. It reports which tables answered, and it never stores any data it reads.

For the wider picture of what AI builders leave to you, see what Lovable and Bolt don’t set up for you. If you are also tidying up your hosting, security headers in 10 minutes is a good next job.

Check your site

Run a free scan to see what your site exposes in public. If it uses Supabase, verify ownership and Patch will try the drawers for you.

Free scan

See what Patch finds on your site.

Free, no signup. You get a score, every issue with its severity, and full detail on the top three.

No signup needed. Not legal advice; every check names its source.

More guides