New row violates row-level security policy.
PostgreSQL refused the write because no policy let this user write this row. In a Supabase app that comes down to one of six causes, and three things tell you which: the table named in the error, the role making the request, and whether you asked for the row back.
What you are looking at.
{
"code": "42501",
"message": "new row violates row-level security policy for table \"profiles\""
}
What is error 42501? 42501 is the PostgreSQL error code
insufficient_privilege.
PostgreSQL raises exactly this message, with exactly this code, when a new row fails a row
level security check; you can see it in
its executor's source.
The same code also covers permission denied for table, which is a missing grant
rather than a policy. That case is further down.
Six causes. Check them in this order.
There is no insert policy for your role
With row level security on, PostgreSQL refuses anything no policy allows. In
its own words:
"If no policy exists for the table, a default-deny policy is used, meaning that no rows
are visible or can be modified." Check the table's policies in your dashboard: is there an
INSERT (or ALL) policy for the authenticated role? If not, add one:
create policy "insert own rows" on public.profiles for insert to authenticated with check ((select auth.uid()) = user_id);
The row you send fails the check
The policy exists, but the new row does not pass its WITH CHECK. The usual
reason is that the app never sends user_id, or sends a different one.
PostgreSQL:
"Rows being inserted that do not pass this policy will result in a policy violation error,
and the entire INSERT command will be aborted." Send the signed-in user's id, or let the
database fill it in so the app cannot get it wrong:
alter table public.profiles alter column user_id set default auth.uid();
The request is not signed in
Your policy says to authenticated, but the request reached the database as
the anonymous role: the client had no session yet, or a server route built its client
without the user's session.
Supabase:
"Once RLS is enabled, no data is accessible through the API when using a publishable key,
until you create policies." Check that a session exists right before the insert, and on
the server build the client from the request's cookies. Do not widen the policy to
anon to make the error go away.
You chained .select() after .insert()
Supabase's client
returns nothing from an insert by default: "By default, inserted rows are not returned. To
return it, chain the call with .select()." Asking for the row back adds a
RETURNING clause, and then
PostgreSQL
also requires a SELECT policy the new row passes: "If a newly inserted or updated row does
not satisfy the relation's SELECT policies, an error will be thrown." That is why the same
insert works without .select() and fails with it. Add a select policy, or drop
.select():
create policy "select own rows" on public.profiles for select to authenticated using ((select auth.uid()) = user_id);
It is an upsert
An upsert is an insert that may turn into an update, so it needs an update policy as well as an insert policy. When the row already exists, PostgreSQL checks it against your update policies and, as its documentation says, "unlike a standalone UPDATE command, if the existing row does not pass the USING expressions, an error will be thrown." It needs a select policy too: the same page says "the rows proposed for insertion are checked using the relation's SELECT policies", and on a real PostgreSQL an upsert of a new row with no select policy was refused with 42501. Add the update policy:
create policy "update own rows" on public.profiles for update to authenticated using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
It is a file upload
If the table in the error is objects, the refusal came from Supabase Storage,
not your own table.
Supabase:
"By default Storage does not allow any uploads to buckets without RLS policies. You
selectively allow certain operations by creating RLS policies on the storage.objects
table." This lets signed-in users upload into one bucket; overwriting with upsert also
needs SELECT and UPDATE policies. The four upload causes, a folder-scoped policy and
the upsert policies are on
the Storage upload page:
create policy "signed-in users can upload" on storage.objects for insert to authenticated with check (bucket_id = 'your-bucket');
Two fixes that trade the error for a leak.
Do not switch row level security off. The error goes away because nothing is checked any more. Supabase gives its API roles access to tables in the public schema by default, so without RLS anyone holding your public key can read and write the table.
Do not move the insert to the secret key in the browser. Supabase: "A secret key authorizes access through the service_role Postgres role, which has the bypassrls attribute. Never use a secret key in the browser or expose it to customers."
Same code, different cause.
If the message is permission denied for table, no policy was ever consulted:
the role has no grant on the table at all. Supabase's own
troubleshooting page
explains that by default "tables in the public schema are granted SELECT, INSERT, UPDATE, and
DELETE to the anon and authenticated roles". A table in a custom schema, or one whose grants
were revoked, has to be granted again. The auth and vault schemas are
closed to the API on purpose, so reach them through a security definer function instead.
grant select, insert, update, delete on table public.profiles to anon, authenticated;
Straight answers.
- Why does the same insert work in the SQL editor?
- Because the dashboard runs your queries as the
postgresrole (Supabase: "Queries you run in the Dashboard execute as postgres"), the role that created, and so owns, your tables. PostgreSQL lets table owners skip row security: "Table owners normally bypass row security as well." Your users are not table owners, so the same insert is checked for them. When the app's request carries no session at all,auth.uid()is null and every ownership check fails; that case has its own page. - What does error 42501 mean in Supabase?
- 42501 is PostgreSQL's code for insufficient_privilege. With the message "new row violates row-level security policy", a row you tried to write failed a row level security check. With "permission denied for table", the role has no grant on the table.
- Is it safe to just turn RLS off?
- No. Supabase gives its API roles access to tables in the public schema by default, so a table without row level security is readable and writable by anyone holding your public key. Fix the policy instead.
One broken policy is rarely alone. 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 auditorAlso seeing 42P17, infinite recursion detected in policy, or 42501 permission denied for table with no policy named in the message?