Skip to main content
You wrote a policy. Before you ship it to a thousand users, you want to confirm it does what you think it does. The trick is that PostgREST sets the role and JWT claims for you on real requests. In a SQL session you have to do that setup by hand. This guide shows how to impersonate anon and authenticated (with any auth.uid() you choose) from psql or the Studio SQL Editor, then run your reads and writes against the policy to confirm it accepts the right things and rejects the wrong things. For the policies themselves, see the RLS Cookbook. For the model that determines who gets which role, see RLS Model.

How PostgREST sets the session

When PostgREST receives a request, it does roughly this on the connection before running your query:
To mimic that in your own session, run the same two statements. SET LOCAL scopes the change to the current transaction, so wrap your testing in a BEGIN ... ROLLBACK block and you can iterate without polluting other connections.

Test as the anon role

The ROLLBACK undoes the SET LOCAL ROLE and any rows you happened to insert that the policy let through. Use it religiously while iterating. Without it, you’ll accidentally leave the session in an unexpected role and the next query will mislead you.

Test as a specific signed-in user

Test custom claims (for auth.jwt() policies)

If you wrote a policy that reads auth.jwt() ->> 'app_role', you set that claim in the same JSON blob:
For a non-admin claim, switch the value:

Test as service_role

service_role bypasses RLS, so testing as it confirms the row exists at all (independent of policies):
If service_role returns a row but authenticated doesn’t, the policy is doing its job. If service_role doesn’t return it either, the row doesn’t exist, and you have a different bug.

A small harness for iteration

Put this in a SQL file and re-run it as you tweak policies:
In the Studio SQL Editor, run each block as a separate snippet. The editor doesn’t preserve session state across “Run” presses, so you re-SET LOCAL every time.

Common things that aren’t what they seem

  • Querying auth.users directly returns rows because the auth.users table is owned by supabase_auth_admin, not authenticated. Don’t be fooled: policies on tables you create do apply.
  • RAISE NOTICE 'auth.uid is %', auth.uid(); doesn’t fail if the role/claims aren’t set; it just prints NULL. If you’re getting unexpected RLS denials, add a SELECT auth.uid(), auth.role(); at the top of your test block to confirm what the session thinks it is.
  • SET ROLE (without LOCAL) persists across the session. If you accidentally use it and later wonder why your policies are denying everything, run RESET ROLE and you’ll be back to the connecting user.
  • The Studio SQL Editor runs as postgres (the project owner) by default. Without SET LOCAL ROLE, RLS is bypassed entirely and every query looks like it works. Always SET LOCAL ROLE first.

Next steps

RLS Cookbook

The five core patterns this guide is for testing.

RLS Model

Roles, JWT claims, and how PostgREST sets them on real requests.