Supabase "permission denied for table" (42501) on a new table
From October 30, 2026, new Supabase tables are not reachable from supabase-js until you GRANT access. Why your new Lovable, Bolt, or Cursor feature returns 42501, and the safe way to fix it without exposing every table.
What you are seeing
- A feature that uses a table you just added fails with
{"code":"42501","message":"permission denied for table your_table"}. - The error comes with a hint like
GRANT SELECT ON public.your_table TO anon;. - Older tables keep working. Only tables created recently, by you, a migration, or your AI tool, fail.
- It works in the SQL editor (which runs as an admin role) but not from the app.
- Supabase's Security Advisor flags tables that are missing grants.
Why it happens
- Supabase changed a platform default. Per its changelog, new tables in the
publicschema no longer get automatic grants for theanon,authenticated, andservice_roleroles. New projects got this from May 30, 2026, and it is applied to all existing projects on October 30, 2026. - Your AI tool or migration created the table with
create tableand policies but nogrant, because that used to be enough. - Grants and RLS are separate layers. The grant decides whether a role can touch the table at all, and RLS decides which rows it sees. A missing grant fails before any policy runs, so editing policies will not help.
- Existing tables keep the grants they already have, which is why only new features break.
- If you only reach Postgres through a direct connection (an ORM or server with a connection string), this change does not affect you.
How to fix it
1. Confirm it is the grant, not RLS
The error code 42501 with "permission denied for table" means a missing grant. An RLS problem looks different: "new row violates row-level security policy" on writes, or an empty array on reads (see the RLS guide).
2. List tables in public that the API roles cannot reach
select c.relname as table_name,
c.relrowsecurity as rls_on,
has_table_privilege('authenticated', c.oid, 'select') as auth_can_select,
has_table_privilege('anon', c.oid, 'select') as anon_can_select
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r'
order by 1;
3. Grant only what each table needs, and only to the right role
-- signed-in users only (most app tables)
grant select, insert, update, delete on public.your_table to authenticated;
-- add anon only if logged-out visitors must read it
grant select on public.your_table to anon;
-- server code using the service role key
grant select, insert, update, delete on public.your_table to service_role;
4. Turn on RLS and add policies in the same migration
alter table public.your_table enable row level security;
create policy "owner can read" on public.your_table
for select to authenticated using (auth.uid() = user_id);
create policy "owner can write" on public.your_table
for insert to authenticated with check (auth.uid() = user_id);
5. Avoid the quick fix that leaks
Do not paste grant ... on all tables in schema public to anon, and do not restore the old default privileges, just to make the error go away. That re-exposes every table, including any with RLS off, to anyone holding your public anon key. Run the query from step 2 first and make sure every table you grant has rls_on = true.
6. Tell your AI tool about the new rule
Add this to your project rules (.cursorrules, Lovable knowledge, or Bolt prompt): "Every new table in public needs explicit GRANT statements to authenticated (and anon only if public), plus ENABLE ROW LEVEL SECURITY and policies, in the same migration."
How to know it is fixed
Re-run the query from step 2: every table your app uses shows true for the roles it needs and true for rls_on. Then use the feature while signed in and while signed out, and confirm signed-in users see only their own rows and logged-out visitors get nothing they should not.