service_role gets "permission denied for table"
The secret key skips policies. It does not skip grants. Add one line.
Why the "admin" key is refused
Supabase's API key guide puts it in one sentence: A missing grant returns a permission error, including for service_role, whereas a policy that matches no rows returns an empty result.
| Check | anon / authenticated | service_role |
|---|---|---|
| 1. Usage on the schema | Needed | Needed |
| 2. Grant on the table | Needed | Needed |
| 3. Row level security policies | Applied | Skipped (the role has BYPASSRLS) |
Before the 2026 change every new public table was granted to all three API roles, so step 2 was invisible. The statements Supabase runs to switch a project over revoke the automatic grants from service_role as well as from the other two. A table created afterwards is closed to your server code until you grant it.
In our test a service_role with BYPASSRLS and no grant got 42501. After grant select it read every row of a table whose policies let authenticated see none.
Confirm it
select
c.relname as table_name,
c.relowner::regrole as created_by,
has_table_privilege('anon', c.oid, 'select') as anon_can_select,
has_table_privilege('authenticated', c.oid, 'select') as authenticated_can_select,
has_table_privilege('service_role', c.oid, 'select') as service_role_can_select
from pg_class c
where c.relnamespace = 'public'::regnamespace
and c.relkind in ('r', 'p')
order by 1;
false in the last column is your answer.
The fix
grant select, insert, update, delete on public.your_table to service_role;
For a table only your server should touch, the whole block is:
-- public.your_table: private
alter table public.your_table enable row level security;
revoke all on table public.your_table from anon, authenticated;
grant select, insert, update, delete on table public.your_table to service_role;
-- no policies: with RLS on and no policy, anon and authenticated read nothing.
-- service_role bypasses RLS. Keep its key on your server only.
Tables with a serial id also need the sequence: see permission denied for sequence.
Dates are from the Supabase changelog post published 2026-04-28, read 2026-11-10. We have not seen an official post confirming that the 2026-10-30 step finished for every project, so check your own project with this query.
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
Does this affect Edge Functions and server routes?
It affects any code that reaches tables through the Data API with the secret or service_role key, wherever it runs. Code that uses a direct Postgres connection string is not affected, per the Supabase changelog.
Should I grant to service_role on every table?
On every table your server code reads or writes through the Data API. Leaving it off a table is a valid way to keep that table out of the API entirely.
Is the new sb_secret key different?
No. Supabase documents that a secret key resolves to the service_role Postgres role, the same role the legacy service_role key uses.
Sources
- Supabase docs: Understanding API keys, read 2026-11-10
- Supabase changelog: Breaking Change: Tables not exposed to Data and GraphQL API automatically (published 2026-04-28), read 2026-11-10
- Supabase docs: Postgres Roles, read 2026-11-10
- Our own test: the behaviour the reference pages describe, run against PGlite, read 2026-11-10
Related
- Private, server-only tableNo access from the browser. Your server reads and writes.
- Which role does each Supabase key use?The key picks the Postgres role. The role needs the grant.
- Grants vs row level securityTwo checks run on every request. A grant comes first.
- 42501, PGRST205, 42P01: which error is yours?Match the code in the response. See its HTTP status and whether a grant is the fix.