Fix Supabase "permission denied for table" (42501) on new tables after Oct 30, 2026
Updated · Applies to: Supabase, Lovable, Bolt, v0, Cursor, Replit, Claude Code
TL;DR fix: Supabase no longer auto-grants new public tables to anon, authenticated and service_role. That is already the default for projects created since May 30, 2026, and it reaches all existing projects on October 30, 2026. Tables that already exist keep their grants. For each new table, put three things in the same migration: the minimum GRANTs each role needs, enable row level security, and policies that check (select auth.uid()). Do not "fix" it with grant all ... to anon.
Steps
- Confirm it is a missing grant, not an RLS policy. The API error has code 42501 and the message permission denied for table your_table, with a hint such as GRANT SELECT ON public.your_table TO anon. If the message instead says new row violates row-level security policy, the grant exists and a policy is blocking the row; that is a different fix.
- Check which roles have privileges on the table. In the SQL editor run: select grantee, privilege_type from information_schema.role_table_grants where table_name = 'your_table'; A new table with no rows for anon or authenticated is not reachable through supabase-js.
- Decide what each role really needs. Logged-out visitors (anon) usually need nothing, or SELECT only for genuinely public data. Signed-in users (authenticated) get only the operations your app performs. service_role is for server-side code. Never grant to anon just because the hint suggests it.
- Write grants, RLS and policies in one migration. Supabase's changelog says to treat the grant, enabling RLS, and the policies as a unit. Use the SQL below and keep it in a migration file so it is reviewable.
- Grant the sequence only if you use serial ids. Supabase also stops default privileges on new sequences. Tables with a serial or bigserial id need grant usage, select on the sequence for roles that insert; identity columns (the Supabase dashboard default) do not.
- Retest as a real user and run the Security Advisor. Log in through the app (the SQL editor bypasses grants and RLS), retry the action, then check the Data API exposure badge in Table Editor and the Security Advisor in the dashboard.
What changes, and when
From the official Supabase changelog:
“Once the change is rolled out to your project, new tables you create in
publicschema require an explicit opt-in (via a Postgresgrant) before the Data API can see them. Existing tables are not affected in your project, they keep their current grants and stay reachable.”
| Date | What happens (per Supabase) |
|---|---|
| April 28, 2026 | Opt-in setting available at project creation |
| May 30, 2026 | New behavior becomes the default for new projects (gradual rollout) |
| October 23, 2026 | Final notice email to project owners and admins |
| October 30, 2026 | New behavior enforced on all existing projects |
It affects you if your app uses the Data API (supabase-js, any client library, or /rest/v1/ and /graphql/v1) and new tables get created in public without explicit grants. Supabase names AI coding tools directly: “Your AI coding tool, CLI script, or Management API call creates tables and expects them to be reachable over the Data API on creation.”
The error you’ll see
When a grant is missing, Supabase documents this PostgREST response:
{
"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’s troubleshooting guide notes 42501 errors are “often reported by clients as 401 or 403 errors.” In the app it usually shows up as an empty list, a failed save, or a toast saying “permission denied.” Postgres stops the request before Row Level Security runs, so rewriting your policies won’t help.
The unsafe “fix” AI tools reach for
Paste that error into an AI tool and the quickest way to make it go away is to open everything up:
-- DON'T: makes the error disappear by giving the public key every privilege
grant all on public.your_table to anon;
grant all on all tables in schema public to anon, authenticated;
alter table public.your_table disable row level security;
anon is the role behind your public (anon or publishable) key, which ships in your frontend. GRANT ALL also gives TRUNCATE, REFERENCES and TRIGGER, which the Data API never needs. If RLS is off or a policy says using (true), anyone can read or rewrite the table. Even the hint’s suggested GRANT SELECT ... TO anon is only right if logged-out visitors should actually read that table.
The safe fix: grants, RLS and policies in one migration
Supabase’s changelog says to “treat these three steps as a unit.” Here it is for a typical per-user table (rename notes and user_id):
-- 1. Least-privilege grants: only what the app actually does
-- (no anon grant: logged-out visitors can't touch this table)
grant select, insert, update, delete on public.notes to authenticated;
grant select, insert, update, delete on public.notes to service_role;
-- 2. Row Level Security on
alter table public.notes enable row level security;
-- 3. Policies that tie every row to the signed-in user
create policy "users read own notes"
on public.notes for select to authenticated
using ((select auth.uid()) = user_id);
create policy "users insert own notes"
on public.notes for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "users update own notes"
on public.notes for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "users delete own notes"
on public.notes for delete to authenticated
using ((select auth.uid()) = user_id);
-- Optional: fill user_id automatically
alter table public.notes alter column user_id set default auth.uid();
-- Only if the id is serial/bigserial (not an identity column):
-- grant usage, select on sequence public.notes_id_seq to authenticated;
For a genuinely public read-only table (say, a product catalog), grant select to anon and authenticated, enable RLS, and add a for select policy. Don’t grant insert, update or delete to anon.
Supabase’s docs say the same thing: “Bundle grants with your RLS setup in the same migration. The grant command controls role access. The enable row level security command and policies control row access.”
Prompt for Cursor, Lovable, Bolt or Claude Code
Supabase returns 42501 "permission denied for table <table>" for a new table.
Since Supabase's Oct 30, 2026 change, new public tables have no default grants.
Fix it safely in ONE migration:
1. Grant only the privileges each role needs. No GRANT ALL. No grants to anon
unless logged-out visitors must read this table, and then SELECT only.
2. alter table public.<table> enable row level security;
3. Add select/insert/update/delete policies for authenticated that check
(select auth.uid()) = user_id (USING and WITH CHECK as appropriate).
Do NOT disable RLS, do NOT use USING (true) / WITH CHECK (true).
From now on, every new table you create must include its grants, RLS and
policies in the same migration. Show me the SQL before running it.
Want the new behavior now?
Supabase’s changelog gives the statements to opt an existing project in before October 30 (run in the SQL editor). Existing objects keep their grants:
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
More background: Supabase’s October 30, 2026 change explained and check your Supabase RLS in two minutes.
When to get help
Get help if a lot of tables or migrations were created by an AI tool and you're not sure which ones should be public, if your agent keeps adding GRANT ALL or disabling RLS to make the error go away, or if your platform provisions Supabase projects for customers and the provisioning flow needs grants added before October 30.
Want us to look at your app?
VibeCPR Rescue Reports are launching soon: we scan your repo and live app, and a human verifies every critical finding. Join the waitlist for early access and founder pricing. You'll also get Patch Notes, our weekly fixes newsletter.
FAQ
What exactly changes in Supabase on October 30, 2026?
Supabase's changelog says that on October 30, 2026 the setting will be applied to all existing projects: new tables created in the public schema require an explicit GRANT before the Data API (REST, GraphQL, supabase-js) can see them. Projects created since May 30, 2026 already behave this way by default. Existing tables are not affected and keep their current grants.
Will my existing tables break on October 30?
No, according to Supabase: "Existing tables are not affected in your project, they keep their current grants and stay reachable." The break happens the first time you or an AI tool creates a new table in public without an explicit GRANT.
Why not just run GRANT ALL ON the table TO anon?
GRANT ALL hands anon (anyone holding your public key) every table privilege, including TRUNCATE, REFERENCES and TRIGGER, which the Data API never needs. If RLS is off or a policy is too loose, anyone can read or rewrite the table. Grant only the operations each role uses, and keep RLS on with policies.
Is this the same as "new row violates row-level security policy"?
No. Both are 42501, but "permission denied for table" means the role has no GRANT on the table, so Postgres rejects the query before RLS is checked. "new row violates row-level security policy" means the grant exists and an RLS policy blocked the write.
Does this affect direct Postgres connections or other schemas?
Supabase says you are unaffected if you only use a direct Postgres connection (psql, an ORM, or a server with a connection string). Tables in storage, auth, realtime and custom schemas keep their current grants and defaults.
Can I switch to the new behavior before October 30?
Yes. Supabase's changelog gives alter default privileges ... revoke statements for the postgres role in the public schema. Existing objects keep their grants, so your running app stays reachable; only future tables need explicit grants.
Sources
- Supabase changelog: Breaking Change: Tables not exposed to Data and GraphQL API automatically
- Supabase docs: Securing your API
- Supabase troubleshooting: Database API 42501 errors
- Supabase docs: Row Level Security
Related fixes
- Fix "Your Cloud Firestore database has insecure rules" (Firebase email)
- Fix Stripe webhook "No signatures found matching the expected signature for payload"
- Fix Supabase Edge Function "blocked by CORS policy" (preflight)
- Fix "new row violates row-level security policy for table" (Supabase)
- Fix "404: NOT_FOUND" on refresh in a React/Vite app on Vercel
- Fix "Module not found: Can't resolve" on Vercel (works locally)