TIC Association
Supabase · auth.uid() and row level security

auth.uid() is null, and your policies see nobody.

Row level security compares auth.uid() with the row's owner. When the request carries no JWT, auth.uid() is null, every comparison is false, inserts fail with 42501 and selects come back empty with no error at all. The SQL editor hides it too: on its default role it runs as postgres, which row security lets through.

the symptom

Three faces of one missing session.

from the app, insert:  42501 new row violates row-level security policy for table "profiles"
from the app, select:  []   no error, no rows
in the SQL editor:     works on its default role

What is auth.uid()? It is the Supabase helper that reads the subject of the JWT that came with the request. In Supabase's words: "When a request is made without an authenticated user (e.g., no access token is provided or the session has expired), auth.uid() returns null". A policy that compares it with the owner column then fails on every row, because, as the same guide puts it, "null = user_id is always false in SQL". Proven on a real PostgreSQL: with no subject on the request the insert is refused with 42501, the select returns 0 rows and no error, and both succeed the moment the subject is present. You can see the value your editor session carries:

select auth.uid();

On the editor's default role it returns null too. There is no JWT there either; that role is just not stopped by it, for the reason in the last section.

find where the session is lost

Three places. Check them in this order.

1

The browser client has no session yet

The query ran before sign-in finished, or after the session expired. Right before the query, ask the client who the user is and log the answer; if it is empty, nothing you do in the policy will help. Do not widen the policy to the anonymous role to make the error go away: that hands the table to anyone holding your public key.

2

A server route built its client without the user's cookies

A server component, route handler or middleware that creates a plain client has no session, so the database sees no user. Supabase: "Server-Side Rendering (SSR) with Supabase requires cookie-based session storage." And: "Use the browser client in code that runs on the browser, and the server client in code that runs on the server." The server client is built from the request's cookies, and that is what carries the JWT to the database.

3

A secret-key client on the server still runs as the user

This one surprises people in the other direction: the request was denied even though the server used the secret key. Supabase: "A secret key bypasses RLS only when the request carries no user access token. If the request carries one, it runs under the RLS policies of that signed-in user, even when the client library was initialized with a secret key." So a secret-key client that also forwards the user's token is checked like the user, and a secret-key client that forwards nothing bypasses every policy. Keep the second kind on the server only.

the policy

Say what you mean: signed in, and the owner.

(select auth.uid()) = user_id already refuses a request with no session, because null never equals anything. The guide still asks for the check to be explicit: "we recommend explicitly checking for authentication". This policy does that for every operation on the table. Proven: with no subject it refuses the insert (42501) and returns 0 rows; with the subject present the same user inserts and reads their own row.

create policy "own rows only"
on public.profiles for all to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id)
with check ((select auth.uid()) is not null and (select auth.uid()) = user_id);
why the editor is different

The editor is the owner, by default.

By default, queries in the dashboard run as postgres. Supabase: "Queries you run in the Dashboard execute as postgres". That role usually owns your tables, and PostgreSQL lets owners through: "Table owners normally bypass row security as well". Proven: the table owner inserts a row with no JWT at all, while the signed-in role with no JWT is refused. So a query that works in the editor on its default role tells you nothing about the policy. To test the policy itself, switch the editor's role dropdown to Authenticated and pick the user. Supabase: "You can use the new Role dropdown to select a different Postgres role for your queries in Studio."

questions

Straight answers.

Why does select auth.uid() return null in the SQL editor?
Because on its default role the editor sends no JWT: auth.uid() reads the subject of the request's JWT, and there is none. It still works on your tables because it runs as postgres, which usually owns them, and row security lets the owner through. Switch the role dropdown to Authenticated and pick a user to test as them.
Why do I get no error, just no rows?
A select is filtered, not refused: rows that fail the policy are simply left out. With auth.uid() null every row fails, so the result is empty and nothing raises. Proven on a real PostgreSQL: 0 rows, no error, and the same select returns the user's row once the JWT subject is present.
Is auth.uid() = user_id wrong?
No, it refuses a request with no session as well, because null equals nothing. Supabase recommends adding the explicit null check so the intention is clear to whoever reads the policy next. The suite runs the explicit form on this page and confirms it refuses and allows exactly as expected.

One policy is rarely the only one. Check every table.

Our free auditor reads your policies and lists the holes worst first, including the quiet ones that never raise an error. It runs in your own SQL editor in about a minute and never touches your rows.

Get the free RLS auditor

Also seeing 42501, new row violates row-level security policy, the same error on a file upload, or 42P17, infinite recursion detected in policy?

Still denied after the session is fixed? Paste the error into the free diagnosis at rescue.ticassociation.com and get the cause and the fix path back in about a minute.

© 2026 TIC Association Privacy · Terms hello@ticassociation.com