New row violates row-level security policy
Postgres refused to save a row because no row-level security policy on that table lets the current user write it.
Also appears as
- new row violates row-level security policy (USING expression) for table "todos"
Prompt to fix it
Saving data fails with "new row violates row-level security policy". Keep row-level security on. Find the table named in the error, read its current policies and the code that writes to it, and add the missing policies so signed-in users can read, insert, and update only their own rows (auth.uid() = user_id). Make the insert set user_id from the current session, or default the column to auth.uid(). If the code uses upsert or .select() after the write, cover those too. Show me the SQL before you run it.
Paste into Layerwise or any AI coding tool.
What it means
Row-level security (RLS) lets a Postgres table decide, row by row, who can read and change data. Supabase turns it on for tables created in the Table Editor, and once it’s on, every action is denied until a policy allows it. This error means an insert, update, or upsert reached the table and no policy accepted the new row.
Reads fail more quietly. A select that RLS blocks returns an empty list with no error, which is why a page can load fine and still show nothing.
The real fix is a policy that matches how the app writes data, usually “signed-in users can write rows where user_id is their own ID.” Turning RLS off also makes the error go away, but then anyone holding your public anon key can read and change every row.
Common causes
- RLS is on and the table has no
INSERTorUPDATEpolicy yet. - The policy checks
auth.uid() = user_id, but the insert leavesuser_idempty or sets it to a different ID. - The request went out without the user’s session, so
auth.uid()is null and the policy forauthenticatedusers never applies. - The code calls
upsert, which needs both anINSERTand anUPDATEpolicy, or chains.select(), which also needs aSELECTpolicy.
How to fix it
- In the Supabase dashboard, open the table named in the error and review its policies.
- Add an
INSERTpolicy for theauthenticatedrole withWITH CHECK (auth.uid() = user_id), and anUPDATEpolicy with the same check inUSINGandWITH CHECK. - Make the insert send
user_idfrom the signed-in session, or give the column a default ofauth.uid(). - Call
supabase.auth.getUser()right before the write and confirm it returns a user. - If the code uses
upsertor.select()after the write, add the matchingUPDATEandSELECTpolicies.
Example fix
-- Signed-in users can manage only their own rows.
alter table todos enable row level security;
-- Newer Supabase projects don't grant API access to new tables on their own.
grant select, insert, update on todos to authenticated;
create policy "Read own todos" on todos
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "Add own todos" on todos
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "Update own todos" on todos
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
-- Fill user_id when the app leaves it out.
alter table todos alter column user_id set default auth.uid();Related errors
All errorsInfinite recursion detected in policy for relation
A row-level security policy reads from its own table, so checking the policy runs the same policy again, without end.
Permission denied for table
The database role making the request has no grant on that table, so Postgres refuses before row-level security even runs.
AuthSessionMissingError: Auth session missing
A Supabase auth call needed a signed-in user, but the client making it had no session, often because it runs on the server without cookies.
Relation does not exist
The query names a table that Postgres can’t find in this database, usually because it was never created here or has a different name.
Build with an agent that checks its work
The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.