Home / RLS / Fix 42501: permission denied for table
Supabase RLS · Error fix
Fix 42501: permission denied for table
The role (anon, authenticated or service_role) has no privilege on the table, so Postgres stops the query before RLS even runs. Grant only that table, only to the role that needs it, in the same migration that creates it. Never fix it with grant ... on all tables.
01 Grants vs RLS: two separate locks
Every query from the Data API passes two checks. Grants decide whether a role may touch the table at all. RLS then decides which rows. 42501 permission denied for table invoices means the first lock failed, so your policies were never consulted. An RLS problem looks different: zero rows, or new row violates row-level security policy.
02 Where it comes from
A table created after Supabase's grants change
New tables no longer get automatic grants for the API roles. The first query to a new table returns 42501 until you grant it. What changed on Oct 30, and why it matters.
A table in another schema
The role also needs usage on the schema, otherwise the message is permission denied for schema app.
The grant was revoked or never given to this role
For example, granted to authenticated but called by a signed-out visitor (anon). The Data API answers 401 for anon and 403 for authenticated.
03 The fix: grant per table, with RLS in the same migration
migration.sql
alter table public.invoices enable row level security; create policy "owner reads" on public.invoices for select to authenticated using (user_id = (select auth.uid())); -- only the role and the operations the app actually uses grant select on public.invoices to authenticated;
For a table in another schema, add grant usage on schema app to authenticated; as well.
04 The fix that leaks
-- don't paste this to make the error go away grant select on all tables in schema public to anon, authenticated;
It removes the 42501 and also grants every other table in public. Any of them with RLS off becomes readable by anyone holding your public anon key. Check what each role can reach before and after any grant:
check.sql
select c.relname, has_table_privilege('anon', c.oid, 'select') as anon_can_read, c.relrowsecurity as rls_on from pg_class c join pg_namespace n on n.oid = c.relnamespace where n.nspname = 'public' and c.relkind = 'r' order by 1;
Any row with anon_can_read = true and rls_on = false is public. The grants change guide has a stricter version that also catches column grants and views.
Catch this before it ships
airlock-rls is a CI gate that fails your build when a table ships exposed or a policy is permissive: the same class of bug, caught in CI against a database with your migrations applied, before it reaches prod.
Or start from nextjs-supabase-starter : auth + a table with RLS + an isolation test, so you start from a pattern that's already sealed.
Grab the Supabase RLS cheat sheet
The golden rules, the footguns that leak in prod, correct policy snippets, and the isolation test — on one page.
FAQ
Is 42501 an RLS error?
No. It's a privilege error that happens before RLS. The same SQLSTATE is also used when an insert or update fails a policy's WITH CHECK, but then the message is new row violates row-level security policy, not permission denied for table.
Do I need to grant to service_role too?
If your backend uses the service_role key on a new table, yes. service_role bypasses RLS but still needs the table privilege.