Supabase insert works, .select() returns 42501
The row saves, but .insert().select() returns 42501? Returning the new row needs SELECT. Add the grant and a SELECT policy that matches that row.
Reviewed October 17, 2026
What it means in plain English
.insert() writes the row and does not ask for it back. Chaining .select() asks the API to return the inserted row. Postgres then runs that as a write with RETURNING. Postgres says RETURNING needs SELECT permission, and the new row must satisfy the table's SELECT policies. If it does not, Postgres throws an error. It does not quietly return zero rows.
Supabase's RLS guide uses SQLSTATE 42501 for two refusals: a missing grant, which is checked before any policy, and a WITH CHECK violation. Read the message. permission denied for table is the grant. new row violates row-level security policy is the policy.
Likely causes
- The role can
INSERTbut was never grantedSELECT. A plain insert works. The.select()call needs the extra grant. - An
INSERTpolicy allows the new row, and there is noSELECTpolicy that lets that same role see it. Postgres applies theSELECTpolicies to the row being returned. - The
SELECTpolicy comparesauth.uid()touser_id, and the inserteduser_iddoes not match the signed-in user. The write passed a looserINSERTpolicy, then the return failed theSELECTpolicy. - The insert fails even without
.select(). That is theINSERTpolicy or theINSERTgrant, not this page. See new row violates row-level security policy and permission denied for table.
How to fix it, step by step
- In the browser Network tab, compare the two calls. The one without
.select()should be success. The one with.select()is the42501. - Grant
SELECTto the role your app uses, usuallyauthenticated:
grant select on table public.todos to authenticated;
- Add a
SELECTpolicy that allows the inserted row. Supabase's pattern for a user's own rows:
create policy select_own_todos on public.todos
for select
to authenticated
using ((select auth.uid()) = user_id);
- Keep the
INSERTpolicy. Supabase'sWITH CHECKexample requires(select auth.uid()) = user_idso a user cannot insert a row for someone else. TheSELECTpolicy has to accept that same row or the return still fails. - If you do not need the new row in the response, stop chaining
.select(). The insert reference says inserted rows are not returned unless you chain it. The row is still saved. - Paste this to the AI tool:
supabase.from(table).insert(row) succeeds, and .insert(row).select() returns 42501. Grant SELECT on that table to the authenticated role. Add a SELECT policy whose USING expression matches the row we insert. Keep the INSERT policy. If the message is "permission denied for table", fix the grant. If the message is "new row violates row-level security policy", fix the SELECT policy. Do not disable RLS.
How to check it worked
.insert().select()returns the new row and no42501.- The row is visible in the Table Editor.
- A signed-out request still cannot read other people's rows.
Sources
Related fixes
More Supabase RLS and permissions fixes
Keep going on your own.
The guides stay free. A deep leak report is $15. Fix packs are $24, or $49 for all three. There is no done-for-you repair.