Which grant does each call need?
Tick what your code does. Copy the smallest grant that makes it work.
grant select, insert on public.your_table to authenticated;
Needs SELECT, INSERT on the table, plus USAGE on the sequence if the id column is serial.
The rule behind each row
| Call | SQL it becomes | Table privileges | Why |
|---|---|---|---|
.select() | select | SELECT | Reading rows needs SELECT. |
.insert() | insert | INSERT | Inserting needs INSERT. Rows are not returned unless you chain .select(). |
.insert().select() | insert ... returning | INSERT, SELECT | Returning the new row needs SELECT on the returned columns as well. |
.update().eq() | update ... where | SELECT, UPDATE | A filter reads columns to find the rows, so UPDATE alone is not enough. |
.upsert() | insert ... on conflict do update | INSERT, SELECT, UPDATE | The conflict path is an update, and the conflict column is read. |
.delete().eq() | delete ... where | DELETE, SELECT | A filter reads columns to find the rows, so DELETE alone is not enough. |
Three of these surprise people:
- Update and delete need SELECT. The PostgreSQL manual:
any nontrivial UPDATE command will require SELECT privilege as well
. The filter in.eq()reads a column. - Upsert needs UPDATE. The manual:
If ON CONFLICT DO UPDATE is present, UPDATE privilege on the table is also required.
- Chaining
.select()needs SELECT. The client reference says:By default, inserted rows are not returned. To return it, chain the call with .select().
Returning rows is a read.
For each call we granted exactly the privileges in the table, ran the statement, then removed one privilege at a time and confirmed Postgres answered permission denied for table.
Tested on the SQL statements in Postgres (PGlite), not through a hosted project. The Data API may send a slightly different statement for the same call; if a call still fails, the hint in the error names the missing privilege.
A grant is half of it
A grant lets the role reach the table. With row level security on, a policy must also allow the rows. See which policy each command needs, or paste your table into the tool below to get grants and policies together.
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 update fail when I granted UPDATE?
Because the statement has a filter. Finding the rows to change reads the table, and reading needs SELECT. Grant SELECT and UPDATE together.
Does insert need a grant on the sequence?
Only when the id column takes its value from a sequence, as serial and bigserial columns do. Then the role also needs USAGE on that sequence. A uuid default needs nothing extra.
Should I just grant all four?
It works, and it gives the role more than it uses. Supabase recommends granting each role only what it needs. If signed-out visitors only read, grant anon SELECT and nothing else.
Sources
- PostgreSQL manual: INSERT, read 2026-11-10
- PostgreSQL manual: Privileges, read 2026-11-10
- PostgreSQL manual: Sequence Manipulation Functions, read 2026-11-10
- Supabase JavaScript client reference: insert, update, upsert, delete, read 2026-11-10
- Our own test: the behaviour the reference pages describe, run against PGlite, read 2026-11-10
Related
- Grants for anon, authenticated and service_roleThree roles, four privileges. This is the grid the tool fills in.
- permission denied for sequenceThe table is granted and its id counter is not. One more grant fixes it.
- Which policy does each command need?One table: every command against the policies Postgres checks for it.
- Postgres ACL decoderPaste the privilege string. Get one row per role, in words.