Skip to content

Security basics

New row violates row-level security policy in Supabase. Now what?

"New row violates row-level security policy" means Supabase refused a write. The fix that clears it in ten seconds also reopens the table to everyone.

Vlad Tkachenko8 min read
A stack of rows inside a table, with one more row waiting outside it, drawn in outline because it has not been let in yet.

In short

  • New row violates row-level security policy means your database checked the rules on that table and found none of them allowing the row you were saving. It refused the write and kept the table as it was.
  • One message covers three different situations: no rules at all, a rule that is only about reading, or a rule about writing that your row does not satisfy.
  • The answer at the top of every search result makes the error stop by allowing every write from anyone. The rule you actually want takes about two more minutes.

Something told you to turn Row Level Security on. The advisor inside your Supabase dashboard, a scan result, a checklist, somebody in a Discord. You turned it on. Now your app cannot save anything at all. Every attempt comes back saying that a new row violates row-level security policy:

new row violates row-level security policy for table "profiles"

Here is the part that guide after guide gets wrong: the answer that clears this error in ten seconds also undoes the thing you just switched on. It is the first result you will find, it works immediately, and it leaves the table exactly where it was before anybody told you to fix it.

What "new row violates row-level security policy" means

Your database was asked to store a row, checked the rules on that table, found none of them allowing that particular row in, and refused.

That is the entire message. Nothing is corrupt and nothing was lost: the row was never saved, and the rest of the table sits exactly as it did a minute ago. The wording comes from Postgres, the database engine Supabase runs on, which is why it reads like machinery rather than like anything your builder wrote. Supabase passes it through with its number attached, 42501.

What the message will not tell you is which rule was missing. One sentence covers three different situations, and telling you which one you hit would describe your rules to whoever triggered the error:

  • The table has Row Level Security switched on and no rules written at all.
  • It has a rule, and that rule is only about reading.
  • It has a rule about writing, and the row you sent does not satisfy it.

All three print that identical line. The second is the common one in a newly launched app, where a rule was added to get the screens working again and saving never came up.

Why it appeared the moment you turned Row Level Security on

Because switching it on refuses everything until a rule says otherwise, and your own app is part of everything.

This is the sequence almost everybody goes through. You turn the setting on. The screens go blank, because with no rules written the database now refuses to hand over rows to anyone, your app included. You add a rule so the lists come back, usually the first one that a search result or your builder suggests. The screens fill up. And the first time somebody presses save, this error appears, from a table you thought you had already fixed.

Nothing went wrong in that sequence. The rule you added was about reading, and saving is a different question that nobody had asked yet.

A rule has two halves, and only one of them is about reading

A policy is a condition, and where the database applies that condition depends on what you asked it to do.

USING is applied to rows already sitting in the table. It decides which ones you are allowed to see, change or remove. WITH CHECK is applied to the row you are trying to create, before it exists anywhere. It decides whether that row is allowed to come into being at all.

That second one is the part that surprises people, because it is a rule about something that is not there yet.

Your app asks toChecked against rows already in the tableChecked against the row being written
read (select)USINGnothing to check
save a new row (insert)nothing to checkWITH CHECK
change a row (update)USINGWITH CHECK
remove a row (delete)USINGnothing to check
The same condition, aimed at two different things. On a read it is asked about rows that exist; on a save it is asked about a row that does not exist yet.

A policy written FOR SELECT only ever carries the first half, because there is no new row to check when somebody is reading. So it can never permit a save, however permissive it looks. That is the whole of the mismatch, and it explains why your reads recovered and your writes did not.

If you would rather see this from the outside, our free scan asks your live database what a stranger can already read from it, without an account and in about 20 seconds: scan your app.

The fix that makes the error stop, and what it costs

The fastest way to clear this is a rule that allows every write from anyone, which is why it is the top answer nearly everywhere you look.

It usually arrives looking like this:

CREATE POLICY "Enable insert for all users"
  ON public.profiles
  FOR INSERT
  WITH CHECK (true);

Every row satisfies true, so every save is allowed, from your app and from anybody else holding the key that ships inside it. Turning Row Level Security back off does the same job more thoroughly.

Both of them work. Both of them leave you where you were before the advisor flagged the table, and neither shows any sign afterwards that something is open, because your app behaves identically in all four combinations of setting and rule. A table can be switched on, carry a valid policy, show a green badge in your dashboard and still hand its rows to a stranger. That is the other half of this problem, and it is worth reading before you paste anything.

The rule that lets your app save, and only your app

Name the person the row belongs to, and compare it to whoever is asking:

CREATE POLICY "Users insert their own rows"
  ON public.profiles
  FOR INSERT
  TO authenticated
  WITH CHECK (auth.uid() = user_id);

auth.uid() is whoever is signed in and making the request. user_id is the column on the row saying who it belongs to. When those two match, the row is saved. When they do not, or when nobody is signed in, it is refused, and the person refused sees the same message you have been looking at.

Two details in that rule earn their place. TO authenticated means the rule applies to signed-in visitors; leave it out and the policy applies to everybody, strangers included. And your app has to actually send the user_id column, because a rule comparing auth.uid() to a value that arrived empty can never match.

When the rule is right and the error still appears

Look at who was signed in before you look at the policy again.

Three different causes reaching you as one sentence. The code 42501 is the same in all three, which is why the message on its own cannot tell you which one you are in.

Three explanations cover most of what is left:

Nobody is signed in. auth.uid() comes back empty for a visitor who has not logged in, so a rule comparing it to an owner column cannot match. This is normal on a signup form, a waiting list or a contact form, and those need their own rule describing what a stranger is allowed to add.

The owner column never arrives. Your rule compares auth.uid() to user_id, and your app sends everything except user_id. The comparison runs against a blank and fails every time.

The write is going to Storage. File uploads land in Supabase Storage, which keeps its own policies on storage.objects rather than on your table. A rule written on profiles has nothing to say about a file.

There is a fourth explanation, and it is the one worth ruling out early: if saving works from your builder's preview and fails from your live site, the two are not using the same key. A secret key ignores every rule you have written, which is what it is for, so an app that only saves while a secret key is in play is an app whose rules have never actually been tested. Which API keys are safe in your frontend covers how to tell one from the other.

What to do today

What to do

  • Read the error as a refusal rather than a fault. The write did not happen, the table is unchanged, and nothing needs recovering.
  • Check whether the table has any policy about writing at all. A rule written FOR SELECT fixed your screens and said nothing about saving.
  • Add a policy FOR INSERT whose WITH CHECK condition names the row's owner, and add the TO clause you meant.
  • Confirm your app actually sends the owner column, then try the save again while signed in.
  • Leave WITH CHECK (true) for tables you would be comfortable publishing on a public page. For anything with a person in it, spend the extra two minutes.

Start with the table the error came from, and check every other table you turned the setting on for the same afternoon. The 10-minute security checklist covers what else tends to be left open in a newly launched app, and the Supabase safety guide goes through the rest of what a stranger can reach.

FAQ

What does "new row violates row-level security policy" mean?

It means your database was asked to store a row, checked the rules on that table, and found that none of them allowed that row in. So it refused the write. Nothing was lost and nothing is broken, because the row was never saved and the rest of the table is untouched. The message comes from Postgres, the database engine Supabase runs on, and it carries the code 42501. What it deliberately does not tell you is which rule was missing, because saying so would describe your rules to whoever triggered the error.

I added a policy and reads work, so why does saving still fail?

Because a policy about reading has nothing to say about writing. A rule carries a USING half, which the database applies to rows already in the table, and a WITH CHECK half, which it applies to the row you are trying to create. A policy written FOR SELECT only ever has the first one, since there is no new row to check when you are reading. Your screens fill up again and the first save still fails. Add a second policy FOR INSERT with a WITH CHECK condition.

Should I just turn Row Level Security off to make this go away?

That does clear the error, and it leaves every row in that table readable by anyone who has the key that ships inside your app. Your app works either way, so nothing afterwards tells you which of the two you chose. If the table holds people, orders or messages, the two minutes it takes to write a real rule is the difference between a private table and a public one.

My policy looks right and inserts still fail. What else could it be?

Three things account for most of these. Nobody is signed in, so auth.uid() is empty and a rule comparing it to an owner column can never match, which is normal on a signup or contact form. Or your app never sends the owner column at all, so the rule compares against a value that arrived blank. Or the write is going into Supabase Storage rather than a table, and Storage keeps its own policies on storage.objects.

Does this error mean somebody tried to attack my app?

Almost never. In a newly launched app it is nearly always your own app being refused, because Row Level Security was switched on before a rule existed that covered your own writes. It is worth reading rather than dismissing, though: the same message appears when a request that should not be writing to that table is refused, and you cannot tell the two apart from the message alone.

Written by

Vlad Tkachenko

Founder, Reeve

I spend my time looking at apps built with Lovable, Bolt, v0, Cursor and Replit, and at the short list of mistakes that keep turning up in them.

More about the author

Read next

Not sure where your own app stands?

Run a free scan and get a plain-language grade from A to F in about 20 seconds. No account, no card.

Scan your app free

Automated external check, not a full audit. Absence of findings is not a guarantee of safety.