TIC Association
Supabase · table privileges

permission denied for table, a grant is missing.

This is not row level security. The database role your app uses (anon or authenticated) has no privilege on the table at all, so Postgres refuses before any policy is read. A correct policy cannot fix it; a grant can. Supabase is also changing a default that makes this error easier to hit.

the symptom

Same code, different error.

from the app:  42501 permission denied for table notes
not this one:  42501 new row violates row-level security policy for table "notes"

What is permission denied for table? It is Postgres refusing a statement because the role running it holds no privilege (select, insert, update or delete) on that table. Supabase's API surfaces it as error 42501.

Both errors carry the code 42501, which is why they get mixed up. The text tells them apart. Supabase: "If you see an error like permission denied for table your_table, the querying role may not have the required privilege for the operation." A row level security violation names a policy. This one names only the table. Proven on a real PostgreSQL: with a correct select policy in place, a role that had its privileges revoked got permission denied for table on a select and on an insert; the policy never ran. A row that fails the insert policy of a role that does have the privilege got the other message, new row violates row-level security policy.

why you may see it more often

New tables stop being granted for you.

Until now a table you created in the public schema was usable by the API roles straight away. Supabase's changelog (April 28, 2026): "New tables in the public schema will no longer be exposed to the Data API automatically." The dates it gives: from May 30, 2026 the setting starts to become the default for new projects (a gradual rollout), and on October 30, 2026 it is applied to existing projects. Existing tables are not affected; only tables created after the change reaches your project need the grant. And the advice: "For new tables you want to expose via the Data API, make explicit grants part of your table-creation flow." A table created by a migration that has no grant for the API roles gets exactly this error on the first query.

confirm it in one query

Ask the database what the role may do.

Run this in the SQL editor, with your table name in place of your_table. If it returns f, the role has no select privilege and a grant is the fix. Proven: it returned f after the privilege was revoked and t after it was granted back.

select has_table_privilege('authenticated', 'public.your_table', 'select');

To see every privilege the API roles hold on the table:

select grantee, privilege_type from information_schema.role_table_grants
where table_schema = 'public' and table_name = 'your_table'
and grantee in ('anon', 'authenticated', 'service_role');
the fix

Grant the privilege, keep row security on.

A grant decides whether the role may touch the table. Row level security still decides which rows. PostgreSQL: "In addition to the SQL-standard privilege system available through GRANT, tables can have row security policies that restrict, on a per-user basis, which rows can be returned by normal queries or inserted, updated, or deleted by data modification commands." So grant, and keep the policies. Proven: after the grant below, the owner reads their row, another signed-in user gets 0 rows and no error, and a row that fails the insert policy is refused with the policy message.

grant select, insert, update, delete on public.your_table to authenticated;

Grant only what the app needs: for a read-only table, select alone. Never grant to a role to make an error go away on a table that has row level security switched off, because then every row is open to anyone holding your public key. Our free auditor lists tables in that state.

when the grant is there and it still fails

Three look-alikes, each with its own message.

1

A table another role created

Default grants belong to the role that set them up. PostgreSQL: "For most kinds of objects, the initial state is that only the owner (or a superuser) can do anything with the object." Proven: a table created by a different role, with a correct select policy, gave permission denied for table to the signed-in role until it was granted. Run the grant above.

2

A schema other than public

The message changes to permission denied for schema. PostgreSQL: usage on a schema "Essentially this allows the grantee to "look up" objects within the schema." Proven: with select granted on a table in a custom schema but no usage on the schema, the error named the schema; granting usage fixed it. Use your schema's name:

grant usage on schema app to authenticated;
3

A serial column and its sequence

An insert into a table with a serial id also uses a sequence, and the message names it: permission denied for sequence. PostgreSQL: "For sequences, allows use of the currval and nextval functions." Proven: with insert granted on the table but no usage on the sequence, the insert failed naming the sequence, and succeeded after this grant (the sequence is named after the table and column):

grant usage on sequence public.your_table_id_seq to authenticated;
questions

Straight answers.

Is permission denied for table the same as the row level security error?
No. Both carry the code 42501, but permission denied for table means the role has no privilege on the table, and no policy can change that. The row level security error says new row violates row-level security policy and names a policy. Proven on a real PostgreSQL: a correct select policy did not help a role whose privileges were revoked.
Does the service role need grants too?
Yes. The service role skips row level security but not privileges. Proven on a real PostgreSQL: after its privileges on a table were revoked, the service role also got permission denied for table.
Why did my new table work before and fail now?
Supabase's changelog says new tables in the public schema will no longer be exposed to the Data API automatically, from May 30, 2026 as the default for new projects (gradual rollout) and on October 30, 2026 for existing projects, and only for tables created after the change reaches your project. Add the grant to the migration that creates the table.

One missing grant is easy. The next one may be a hole.

Our free auditor names every table whose policies never run because a grant is missing, and prints the GRANT for each, alongside the row level security holes, worst first. It runs in your own SQL editor in about a minute and never touches your rows.

Get the free RLS auditor

Also seeing 42501, new row violates row-level security policy, auth.uid() returning null, or an app that works locally and not in production?

Still denied after the grant, or would rather hand it off? Paste the error into the free diagnosis at rescue.ticassociation.com, or send us the project: you pay only when the fix works.

© 2026 TIC Association Privacy · Terms hello@ticassociation.com