Symptoms
- A table you (or your AI builder) just created returns 401/403, and the body says
"code": "42501","message": "permission denied for table ...". - Older tables keep working; only new ones fail.
- The response includes a hint such as
Grant the required privileges to the current role with: GRANT SELECT ON public.your_table TO anon;(Supabase changelog). - Your page loads but a list stays empty, or a form shows a generic error when saving.
Why it happens
Supabase's Data API (what supabase-js calls) runs your queries as the anon or authenticated Postgres role. Two separate checks apply:
- Grants: can this role touch this table at all?
- RLS policies: which rows can it see or change?
Until now, Supabase granted select, insert, update and delete on every new public table to anon, authenticated and service_role by default. Supabase's changelog says that is changing: the new behaviour started rolling out as the default for new projects on 30 May 2026 (gradually, over a few weeks), and on 30 October 2026 it is applied to all existing projects. After that, "new tables you create in public schema require an explicit opt-in (via a Postgres grant) before the Data API can see them. Existing tables are not affected" (Supabase changelog: tables not exposed automatically).
The changelog calls out AI tools directly: "agents, CLI scripts, and AI platforms create tables too, and many of those operations do not have a human reviewing the diff."
Other 42501 causes from Supabase's troubleshooting page (Database API 42501 errors): querying a restricted schema such as auth or vault, or a custom schema you haven't exposed, table privileges someone changed in Integrations > Data API, or column-level restrictions (a select * that includes a restricted column also fails with 42501).
Step-by-step fix
1. Confirm it's a grant problem
The error message names the table, and the hint names the role. You can also list recent 42501 errors in the SQL Editor with the query on Supabase's 42501 troubleshooting page.
2. Grant, enable RLS and add policies, as one unit
Supabase's changelog says to "Treat these three steps as a unit." Adapted from its example, for a table notes with a user_id column:
-- 1. Grant only what each role needs
grant select, insert, update, delete on public.notes to authenticated;
-- grant select on public.notes to anon; -- only if logged-out visitors must read it
grant select, insert, update, delete on public.notes to service_role;
-- 2. Make sure RLS is on
alter table public.notes enable row level security;
-- 3. Policies decide which rows
create policy "Users read their own notes"
on public.notes for select to authenticated
using ((select auth.uid()) = user_id);
create policy "Users add their own notes"
on public.notes for insert to authenticated
with check ((select auth.uid()) = user_id);Don't grant to anon unless logged-out visitors really need the table. A grant without RLS exposes the whole table.
3. Don't grant everything back blindly
It's tempting to re-grant all privileges on all tables to anon. That undoes the safety the change was made for. Grant per table, per role.
4. Tell your AI tool about grants
Every new table needs these statements. The changelog suggests updating your AI tool's instructions, or using Supabase's agent skill, which "includes the grants step." Add a line to your project rules (see the prompt below).
5. If inserts then fail on a sequence
If you see permission denied for sequence ... after the table grant, the role that inserts also needs usage, select on that table's sequence, which is common with serial id columns: grant usage, select on sequence public.notes_id_seq to authenticated; (use your sequence's real name). Supabase's own opt-in and rollback SQL in the changelog handles sequences alongside tables in the same way.
Fix prompt for your AI builder
Paste this into Lovable, Bolt, Cursor, Replit, v0 or whichever tool you build with:
My Supabase table "<TABLE>" returns 42501 "permission denied for table". Supabase no longer grants new public tables to API roles automatically. Fix it with one SQL migration:
1. GRANT select, insert, update, delete ON public.<TABLE> TO authenticated; and TO service_role. Grant SELECT to anon ONLY if logged-out visitors must read this table (tell me which you chose and why).
2. ALTER TABLE public.<TABLE> ENABLE ROW LEVEL SECURITY;
3. Add RLS policies so each signed-in user can only select/insert/update/delete rows where user_id = (select auth.uid()).
Do not grant privileges on other tables, do not disable RLS, and do not use the service_role key in frontend code.
From now on, every time you create a table, include the GRANT, ENABLE RLS and policy statements in the same migration.
Show me the SQL before running it.How to confirm it's fixed
- Reload the page that uses the table while signed in as a test user. Data should load and saves should work.
- Sign out and reload. If you didn't grant to
anon, the request should fail rather than return data. - In the Supabase dashboard, check the Security Advisor. Until 30 October it flags affected tables and shows the SQL to fix them, according to the changelog. The Table Editor's Data API exposure badge also shows which tables the API can reach.
How to find out sooner next time
Heedza never submits forms or writes to your database, so it won't see a failing save. What it checks is whether your landing, signup, login and checkout pages load and show their forms on a schedule, with a screenshot and a fix prompt in an alert when one doesn't. The free scan checks your homepage for exposed keys and missing security headers.
Let Heedza check this for you. Heedza’s security checks looks for secret keys left in your page code and private files anyone can open. It checks again every time it checks your pages. It never logs in or fills in forms, so it can’t see errors that only show after that.
Made with Lovable, Bolt, Replit, Cursor or v0? Free for 1 site: start free or see prices.