Supabase permission denied for table (42501) on a new table: the October 30, 2026 grants change and the safe fix
Last updated · By Ember, SniffPup
Short answer: on October 30, 2026 Supabase applies a new default to all existing projects: new tables in the public schema are no longer reachable through the Data API until you grant access with a Postgres GRANT (Supabase changelog). If a table you just created returns 42501 permission denied for table or doesn't show up in your app, add the grants each role needs, and in the same migration turn on Row Level Security and add policies. Grants without RLS open the whole table to anyone holding your public anon key.
Fix a new table in 3 steps
- Grant only what each role needs.
anon,authenticatedandservice_roleeach get their ownGRANT(SQL). - Turn RLS on and add policies in the same migration. A grant lets a role reach the table; RLS decides which rows it gets (why both).
- Check it. List the grants with SQL, test signed out and as a second user, then run a free SniffPup scan of your live URL to see what an anonymous visitor can read (how).
SniffPup looks at your published site the way a stranger would: secret keys in your HTML and same-origin JavaScript (masked in the report), public /.env and /.git files, a downloadable source map and missing security headers. When your site ships its Supabase URL and public anon key, the free scan also sends one read-only HEAD request per table, up to 8, to see whether an anonymous visitor can read at least one row, without downloading rows or writing anything. It doesn't read your grants or policies. A score of 100 means no issues were found in our checks; it is never proof of security. Every critical issue comes with its full fix, free (with no critical, one other fix is free).
What's changing, and who it affects
Until now, a default Supabase project granted select, insert, update and delete on every new table in public to anon, authenticated and service_role, so every table was reachable through the Data API the moment it was created. Per Supabase's changelog:
- April 28, 2026: new projects could opt out of the automatic grants at creation ("Automatically expose new tables").
- May 30, 2026: no automatic grants became the default for new projects, rolled out over a few weeks.
- October 30, 2026: applied to all existing projects. New tables in
publicwithout an explicit grant stop being reachable through the Data API.
Existing tables keep their current grants and keep working. The change covers the Data API: supabase-js and the other client libraries, REST at /rest/v1/ and GraphQL at /graphql/v1. It doesn't touch the storage, auth, realtime or custom schemas, or apps that only talk to Postgres over a direct connection. Supabase gives the reason plainly: AI coding tools, CLI scripts and agents now create tables with no human reviewing the diff, and the old default exposed tables before anyone secured them.
The first time you or your AI coding tool creates a table after the change, requests to it fail with:
{
"code": "42501",
"message": "permission denied for table your_table",
"hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;"
}Supabase recommends logging the whole error object, not just error.message, because the hint names the exact role and privilege (handling errors in supabase-js).
1. Grant only what each role needs
Decide per role. anon is anyone holding your public key, signed in or not. authenticated is any signed-in user, which with open sign-ups means anyone on the internet who makes an account. service_role is your server. From Supabase's Securing your API docs:
-- Signed-in users read and write (RLS still decides which rows) grant select, insert, update, delete on table public.your_table to authenticated; -- Your server code using the service role grant select, insert, update, delete on table public.your_table to service_role; -- Only if signed-out visitors truly need to read this table grant select on table public.your_table to anon;
Leave out the anon line for anything private. Most tables with user data need no anon grant at all, and a table only your server uses needs only service_role. If an insert then fails with permission denied for sequence, the change also stopped default grants on new sequences in public; grant usage, select on that sequence to the roles that insert.
2. Turn RLS on and add policies, in the same migration
Supabase's changelog says to treat the grant, RLS and policies "as a unit". Grants and RLS are separate layers: the grant controls whether a role can reach the table at all, and RLS controls which rows it sees. Postgres checks the grant first, so a missing grant fails before RLS runs.
alter table public.your_table enable row level security; create policy "users can read their own rows" on public.your_table for select to authenticated using ((select auth.uid()) = user_id);
Add one policy per operation you granted (insert, update, delete) with a real owner check, not using (true). Our Supabase RLS policy examples have copy-paste policies for private rows, public read with owner-only writes, teams and admin roles.
Why grants alone are dangerous: with RLS off, a grant is the only gate. grant select ... to anon on a table with RLS off lets anyone with your anon key, which ships in your frontend, read every row, and an insert, update or delete grant lets them change rows. That's the exact situation the change was made to stop. If an AI coding tool suggests a fix, read it before you run it: pasting the hint's GRANT, or grant all ... to anon, authenticated, without turning on RLS turns a broken table into an open one. Supabase's 42501 troubleshooting page says the same: granting privileges allows access through the Data API, so enable RLS and write policies.
3. Check grants, RLS and what a stranger can read
List who has which privilege on a table (from Supabase's 42501 troubleshooting page):
select grantee, privilege_type from information_schema.role_table_grants where table_schema = 'public' and table_name = 'your_table';
Then find tables where a Data API role has a grant but RLS is off. Every row this returns is a table to fix first:
select distinct t.tablename, g.grantee
from pg_tables t
join information_schema.role_table_grants g
on g.table_schema = t.schemaname and g.table_name = t.tablename
where t.schemaname = 'public'
and t.rowsecurity = false
and g.grantee in ('anon', 'authenticated');In the dashboard, the Data API exposure badge in the Table Editor and the Security Advisor list tables worth reviewing; per the changelog, Security Advisor flags affected tables and shows the fix SQL until October 30. Then test the way a stranger would, signed out, with your project URL and anon key:
curl "https://YOUR_REF.supabase.co/rest/v1/your_table?select=*&limit=1" \ -H "apikey: YOUR_ANON_KEY" \ -H "Authorization: Bearer YOUR_ANON_KEY"
42501:anonhas no grant. Right for a private table, and nothing more to do for signed-out access.[]: the grant exists and RLS returned no rows (or the table is empty).- A row: anonymous visitors can read this table. Fine for a public catalog, a leak for profiles, orders or messages.
With a new sb_publishable_ key, send only the apikey header. Then sign in as two test users and make sure user B can't load user A's rows. Supabase's RLS docs cover how grants and policies fit together, and our RLS checker guide walks through the full set of SQL checks. Only test projects you own.
Bake grants into every migration
If you write migrations by hand, an AI coding tool writes them for you, or your platform provisions Supabase projects for customers, put the GRANT, enable row level security and create policy statements for each new table in the same migration. Supabase suggests updating your AI tool's instructions (or using the Supabase agent skill) so it adds grants by default. Grants in code are reviewable and greppable, and they make the difference between anon and authenticated visible in review.
Want the new behavior before October 30? The changelog has the alter default privileges statements to opt an existing project in today, and the statements to roll back if something breaks. Existing tables keep their grants either way.
FAQ
Why does my new Supabase table say "permission denied for table"?
Because the role making the request has no grant on that table. Supabase is turning off the old default that granted select, insert, update and delete on every new public table to anon, authenticated and service_role. Once that change reaches your project, a new table isn't reachable through the Data API (supabase-js, REST at /rest/v1 or GraphQL) until you grant it, and PostgREST answers with error 42501 and a hint naming the missing GRANT.
When does this change reach my project?
Per Supabase's changelog: new projects could opt in from April 28, 2026; it became the default for new projects from May 30, 2026 (rolled out over a few weeks); and it is applied to all existing projects on October 30, 2026. You can also opt in early on an existing project with the revoke statements in the changelog.
Will my existing tables stop working on October 30?
No. Supabase says existing tables keep their current grants and stay reachable. Only tables created in the public schema after the change need explicit grants. Tables in storage, auth, realtime and custom schemas aren't affected, and apps that only use a direct Postgres connection (psql, an ORM or a server with a connection string) aren't affected either.
Can I just run the GRANT from the error hint and move on?
Only if Row Level Security is on for that table with policies you've checked. A grant decides whether a role can reach the table at all; RLS decides which rows it gets. Granting select to anon on a table with RLS off lets anyone with your public anon key read every row, and granting insert, update or delete lets them change rows too. Grant the smallest set each role needs, turn RLS on and add policies in the same migration.
My table has grants and RLS but returns an empty list. Is that the same problem?
No. A missing grant gives a 42501 error. An empty [] with no error usually means the grant is there and RLS filtered out every row, often because the table has RLS on but no policy for that role yet. Add a policy for the rows that role should see.
Can SniffPup tell me whether my tables are exposed?
It tests one part from the outside. When your live site ships its Supabase project URL and public anon or publishable key, the free scan reads the table list that key can see and sends one read-only HEAD request per table, up to 8, to learn whether an anonymous visitor can read at least one row. It never downloads rows and never writes. It doesn't read your grants or policies, test writes, or test signed-in users reading each other's rows, so run the SQL checks in this guide too.
Does a clean SniffPup report mean my Supabase tables are safe?
No. A score of 100 means no issues were found in our checks. It is never proof of security, and it says nothing about tables the scan didn't reach, write access or what signed-in users can see.
Sources: Supabase changelog: tables not exposed to the Data and GraphQL API automatically, Securing your API, Database API 42501 errors, checked October 26, 2026. SniffPup checks what is publicly visible on your site. A clean report is not a guarantee of security.