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.
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.
First run downloads PostgreSQL (~16 MB, cached afterwards) — your SQL is executed locally and never uploaded.
What each identity got back
— empty until you runRows this identity can see will appear here.
Rows this identity can see will appear here.
Rows this identity can see will appear here.
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.