Tools/Supabase RLS Playground
Real PostgreSQL · in your browser · nothing uploaded

Supabase RLS Playground

Most RLS tools read your policy and guess. This one compiles PostgreSQL to WebAssembly, creates your tables in this tab, applies your CREATE POLICY statements, and runs your query four times — as anon, as two different signed-in users, and as service_role. What you see is what Postgres returned.

Start from a scenarioor paste your own below

The baseline that works. Start here, then break it.

What should happen: Each user sees only their own rows. anon sees none.

Who runs the query

Step 3 is executed once per identity. sub becomes auth.uid() inside your policies — change it to test a different user.

anonAn unauthenticated request — no signed-in userno JWT
User ASigned in
User BSigned in — a different person
service_roleServer-side key — holds BYPASSRLSNever expose this key in browser code.{"role":"service_role"}

First run downloads PostgreSQL (~16 MB, cached afterwards)your SQL is executed locally and never uploaded.

What each identity got back

— empty until you run
anonAn unauthenticated request — no signed-in user

Rows this identity can see will appear here.

User ASigned in

Rows this identity can see will appear here.

User BSigned in — a different person

Rows this identity can see will appear here.

service_roleServer-side key — holds BYPASSRLS

Rows this identity can see will appear here.

What you can settle in thirty seconds

Can anon read this table?

The playground runs your query with no session at all. Whatever comes back is public to anyone who opens devtools, because the anon key ships in your client bundle. RLS policy design patterns

Does my policy actually isolate two users?

Two signed-in identities run the same query at the same time. If both columns show the same rows, the policy is not filtering on identity — that is a cross-tenant leak you can see rather than assume. Debugging Supabase RLS issues

Why does my query return zero rows with no error?

Because RLS filters instead of failing. The playground shows the empty result next to service_role returning everything, which is the fastest way to tell "no matching policy" from "no data". Why RLS returns zero rows silently

Why does it work with the service key and break in the browser?

service_role holds BYPASSRLS. It is a persona here precisely so you can watch it ignore every policy you write. service_role bypasses RLS — how to contain it

Why does INSERT fail when SELECT works?

USING governs reads, WITH CHECK governs writes. Run the INSERT preset and read the error Postgres itself produces: new row violates row-level security policy. new row violates row-level security policy

Why "infinite recursion detected in policy for relation"?

Your policy queries the table it protects. The recursion preset reproduces it in one click, and the multi-tenant preset shows the SECURITY DEFINER fix running clean. Fix: infinite recursion in a policy

How it stays honest

PostgreSQL exempts superusers and table owners from row level security. A naive in-browser Postgres would therefore hand back every row and tell you your policy passed. Every persona query here runs under SET LOCAL ROLE, and every RLS-enabled table gets FORCE ROW LEVEL SECURITY, so the owner exemption cannot lie to you.

The auth schema is shimmed the way Supabase defines it — auth.uid(), auth.jwt(), auth.role() and auth.email() all read from request.jwt.claims — so a policy copied straight out of your project runs verbatim. Table privileges are granted to anon and authenticated the way a real Supabase project grants them, which means a permission denied here means the same thing it means there.

Each persona runs inside a transaction that is rolled back, so an INSERT in the query pane is tested by all four identities against identical seed data. There is no backend: the SQL you paste never leaves the tab, and once the WebAssembly build is cached the tool works offline.

Two honest limits. The engine is a recent PostgreSQL build, which may be a major version ahead of the one your Supabase project runs — row level security semantics are the same, but a version-specific feature might not be. And this is Postgres alone: PostgREST, the API gateway, and Supabase Auth are not here, so it settles what your policies do, not what your HTTP layer does with them.

Found the broken policy?

Walk the rest of the list before you deploy — the checklist covers the policies, the GRANTs, the session plumbing and the cache behaviour that decide whether RLS holds in production.