Supabase "Permission Denied" on New Tables: the October 30 Fix
Key takeaways
- On October 30, 2026, Supabase stops granting new tables in the public schema to anon, authenticated and service_role in existing projects. New projects have worked this way since May 30.
- Tables you already have keep their grants and keep working. Only tables created after the change are affected.
- A new table without grants fails with error 42501, "permission denied for table", in supabase-js, the REST API and GraphQL.
- The fix is one grant per role in the same migration that creates the table, next to row-level security and its policies.
- Grants decide whether a role can reach a table at all; RLS policies decide which rows it sees. You need both.
Pricing an app? Try the free app cost calculator or get a range for your project in the instant quote.
From October 30, 2026, a table you add to an existing Supabase project is invisible to your app until you grant it. Supabase used to give every new table in the public schema to the anon, authenticated and service_role roles automatically. It now leaves that to you. Tables you already have keep working; a table created after the change returns permission denied for table until its migration includes the grants.
To see which tables in your migrations are affected, paste them into the free Supabase October 30 grants checker. It runs in your browser and never uploads your SQL.
What changes, and when
Supabase announced the change in its changelog on April 28, 2026:
| Date | What happens |
|---|---|
| April 28, 2026 | New projects can opt out of exposing new tables when they are created |
| May 18, 2026 | pg_graphql is no longer enabled by default |
| May 30, 2026 | New projects stop exposing new tables by default (rolled out over a few weeks) |
| October 30, 2026 | The same default applies to all existing projects |
What changes is Postgres's default privileges in the public schema. Before, every new table was granted select, insert, update, delete to anon, authenticated and service_role, new sequences got usage, select, and new functions got execute. After the change those defaults are gone, so the Data API (REST at /rest/v1 and GraphQL at /graphql/v1) can't see a new table until a role is granted on it.
What doesn't change:
- Existing tables keep the grants they already have.
- Row-level security works exactly as before.
- Other schemas, such as
auth,storageandrealtime, aren't affected. - Direct Postgres connections (psql, an ORM or your own server) aren't affected. The change matters if your app talks to Supabase through supabase-js, REST or GraphQL.
The symptom
A request to a new table without grants fails with Postgres error 42501:
{
"code": "42501",
"message": "permission denied for table projects",
"hint": "Grant the required privileges to the current role with: GRANT SELECT ON public.projects TO anon;"
}
In supabase-js, the error object comes back filled and data is null. If the app doesn't show errors, users simply see an empty screen or a button that does nothing. The same query works in the SQL editor, which runs as postgres, the role that owns the table and has every privilege, and fails in the app. That is why this one can be confusing.
The fix: grant in the same migration
Put the grants in the migration that creates the table, so every environment (local, staging, production) ends up the same. This is the pattern from Supabase's Securing your API guide:
create table public.projects (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users on delete cascade,
name text not null
);
-- 1. Who can reach the table at all
grant select, insert, update, delete on table public.projects to authenticated;
grant select, insert, update, delete on table public.projects to service_role;
-- 2. Which rows they can see and change
alter table public.projects enable row level security;
create policy "Owners can read their projects"
on public.projects for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Owners can add projects"
on public.projects for insert
to authenticated
with check ((select auth.uid()) = user_id);
create policy "Owners can update their projects"
on public.projects for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "Owners can delete their projects"
on public.projects for delete
to authenticated
using ((select auth.uid()) = user_id);
A few details are easy to miss:
- Grant only what each role needs. A table that signed-out visitors should never touch gets no
anongrant at all. Public read-only data getsgrant select ... to anonand nothing more. serialcolumns need a sequence grant. If the table usesserialorbigserialrather than an identity column or a UUID, inserts also needgrant usage, select on sequence public.projects_id_seq to authenticated;.- Functions called over the API need
execute. For a function your app calls withrpc(), usegrant execute on function public.your_function() to authenticated;. Asecurity definerfunction runs with its owner's rights and bypasses RLS, so grantexecuteonly to roles that should call it, and review those functions. - Grants are not a replacement for RLS. With a grant and RLS off, any signed-in user, or anyone at all if
anonis granted, can read every row. Turn RLS on for every exposed table.
Check your project before October 30
- List your newest migrations (
supabase/migrations/in the repo, or the SQL your AI tool generated) and look forcreate tablestatements inpublicwithout matchinggrantlines. - Run them through the grants checker. It lists each table that would need a grant, each table with RLS off, and each policy that lets anyone in.
- Test as a real user, not as
postgres. Sign in to the app with a normal account and use each feature that touches a new table. The SQL editor bypasses grants, so it won't show the problem. - Opt in early if you can. Supabase lets existing projects switch to the new default before October 30. The changelog gives the SQL to run in the SQL editor; it changes only tables and sequences created afterwards:
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;
Doing it on a staging project first shows you any missing grants on your schedule instead of the deadline's.
If you use Lovable, Bolt or another AI builder
These tools write SQL migrations for you, and nothing in a generated migration warns you that the defaults changed. Read each new migration before you approve it: every create table in public should be followed by a grant for each role that needs it and by enable row level security. If you can't read the SQL yourself, paste it into the checker; it flags missing grants in plain language.
If your AI-built app has more than a couple of these problems, or users are already seeing empty screens, App369's AI-Built App Rescue starts with a $450 Launch Check. Within 3 business days of read-only access, you get each issue it finds in sign-in, data access, payments and deploys, ranked, with where it is, plus a fixed price to fix the worst of them. The $450 is credited to that fix.
Frequently asked questions
- Why does Supabase say "permission denied for table" on a table I just created?
- Since the Data API change (May 30, 2026 for new projects, October 30, 2026 for existing ones), new tables in the public schema have no grants for anon, authenticated or service_role. Add the grants in the migration that creates the table.
- Will my existing Supabase tables stop working on October 30?
- No. Supabase says existing tables keep their current grants. Only tables created after the change need explicit grants.
- Is this the same as a row-level security error?
- No. A missing grant fails every request with "permission denied for table". RLS blocks differently: reads return no rows, updates and deletes on hidden rows change nothing, and a blocked insert fails with "new row violates row-level security policy".
- Can I turn the change off for an existing project?
- Supabase documents a rollback that restores the old default privileges, but that exposes every future table again. Granting each table on purpose is safer.
- Does this affect Lovable, Bolt or other AI-built apps?
- It affects any project that reaches new tables through the Data API (supabase-js, REST or GraphQL), whoever wrote the migrations, which covers most AI-built apps. Apps that only use a direct Postgres connection are not affected. Check new migrations for a grant per role before they run.
Sources
Want this done at a fixed price?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
Related Resources
Related Articles
Supabase "Permission Denied" on New Tables: the October 30 Fix
From October 30, 2026, new Supabase tables return "permission denied for table" (42501) until you grant them. What changes, who is affected, and the exact SQL to fix it.
Read more →Business Application Modernization for Small Businesses (2026)
Business application modernization for small and mid-size companies: how to tell an app needs it, the options from rehosting to a full rebuild, and how to modernize without stopping the business.
Read more →