Which policy does each command need?
One table: every command against the policies Postgres checks for it.
Policies applied by command
| Command | SELECT policy (using) | INSERT policy (with check) | UPDATE policy (using / with check) | DELETE policy (using) |
|---|---|---|---|---|
select | Filters existing rows | Not used | Not used | Not used |
insert | Checks the new row, if the statement reads it back | Checks the new row | Not used | Not used |
update | Filters existing rows and checks the new row, if the statement reads columns | Not used | Filters existing rows / checks the new row | Not used |
delete | Filters existing rows, if the statement reads columns | Not used | Not used | Filters existing rows |
insert ... on conflict do update | Checks existing and new rows | Checks the new row | Checks the existing row / checks the new row | Not used |
Condensed from Table 300 in the PostgreSQL manual, CREATE POLICY, read on the date under Sources. "Reads" means a where or returning clause that refers to the table's columns. The manual's table also covers merge and select for update.
Worked example: the insert that fails only when you ask for the row back
alter table public.notes enable row level security;
grant select, insert on public.notes to authenticated;
create policy "insert own" on public.notes
for insert to authenticated with check ( true );
| Call | Result | Why |
|---|---|---|
.insert(row) | Works | Only the INSERT policy is checked. |
.insert(row).select() | 42501, new row violates row-level security policy for table "notes" | Returning the row reads it, so the new row must also pass a SELECT policy. There is none. |
| Same call after adding a SELECT policy | Works | Both policies pass. |
We ran these three steps in our test and got exactly these results.
Policy error or grant error?
| Message | Layer | Fix |
|---|---|---|
permission denied for table notes | Grant | Grant the role. No policy has run yet. |
new row violates row-level security policy for table "notes" | Policy | Add or correct the policy for that command in the table above. |
Both carry code 42501. The tool below writes the grants and the matching four policies for a table in one block.
Write the fix for your tables
One table is free. The whole schema with a rollback file is $9 one-time.
Waiting for a paste.
Who can reach each table
Your tables show up here, one row each, with a choice of who can reach them.
Parsed in your browser. Your SQL is never uploaded, and no site or database is scanned.
Questions
Why does upsert fail when insert works?
An upsert can take the update path, so it is checked against the UPDATE policy and the SELECT policy as well as the INSERT policy. The role also needs the UPDATE and SELECT grants.
Does service_role need policies?
No. Supabase documents that service_role has the BYPASSRLS attribute, so policies never apply to it. It still needs grants.
Sources
- PostgreSQL manual: CREATE POLICY, read 2026-11-10
- PostgreSQL source: row level security error messages in src/backend/executor/execMain.c, read 2026-11-10
- Supabase docs: Understanding API keys, read 2026-11-10
- Our own test: the behaviour the reference pages describe, run against PGlite, read 2026-11-10
Related
- Grants vs row level securityTwo checks run on every request. A grant comes first.
- Owner-only tableEach signed-in user sees and changes only their own rows.
- Which grant does each call need?Tick what your code does. Copy the smallest grant that makes it work.
- Every cause of 42501 on Supabase42501 means "insufficient privilege". Match your message to a row.