DEV Community

Mateus Zingano
Mateus Zingano

Posted on • Originally published at shipsealed.com

Fix: "new row violates row-level security policy" in Supabase (it's not what you think)

If you've shipped anything on Supabase, you've hit this:


new row violates row-level security policy for table "notes"
​```



Your first instinct is panic — *is my data exposed?* Here's the twist:

**This error is good news.** RLS is **on** and doing its job: deny by default. The dangerous state is the *opposite* — RLS **off**, no error, table public to anyone with your anon key. That's the #1 Supabase leak. So don't reach for the "disable RLS" button. You just need a policy that permits the legitimate write.

There are exactly three reasons the write gets denied:

**Cause 1 — no INSERT policy at all (most common).**
You enabled RLS, added a `SELECT` policy, and moved on. Reads work, writes are denied. RLS is default-deny **per operation** — a read policy does nothing for inserts.

**Cause 2 — the row's `user_id` doesn't equal `auth.uid()`.**
You have `with check (user_id = auth.uid())`, but the client inserted the row without setting `user_id` (so it's `null`), or set a different one. The check evaluates false → violation.

**Cause 3 — the request isn't authenticated.**
Running with the anon key and no session? `auth.uid()` is `null`, so `user_id = auth.uid()` can never be true.

### The fix

Give the table an INSERT policy that lets a signed-in user write **their own** rows, and make the row carry their id automatically:

​

```sql
-- a signed-in user may insert rows that belong to them
create policy "owner inserts own notes" on notes
  for insert
  with check (user_id = (select auth.uid()));

-- stop trusting the client to send user_id — default it server-side
alter table notes
  alter column user_id set default (select auth.uid());
​```



Two gotchas worth internalizing:

- **`USING` vs `WITH CHECK`:** `USING` filters which existing rows you can see/touch; `WITH CHECK` validates the *new* row on INSERT/UPDATE. For inserts, `WITH CHECK` is the one that matters.
- **"It works in the SQL editor but fails from my app."** The SQL editor runs as a privileged role that **bypasses RLS**. Your app runs as `authenticated` / `anon`, which RLS applies to. Always test writes the way your app makes them — signed in, through the client.

I wrote the **full version with a copy-paste test that proves the policy** (and the `UPDATE` case) here: **[Fix: new row violates row-level security policy →](https://shipsealed.com/rls/fix-new-row-violates-row-level-security-policy/)**

Keep RLS on. Ship it sealed. 🦭
Enter fullscreen mode Exit fullscreen mode

Top comments (0)