Why does Supabase return "42501 permission denied for table" on my new table?
Because the table has no grant for the role your request runs as. Supabase's schedule enforces this on all existing projects from Oct 30, 2026: new tables in the public schema no longer get select, insert, update and delete granted to anon, authenticated and service_role automatically. Per Supabase's changelog, a new table "exists but is not reachable via Data API" until a grant says otherwise, and PostgREST answers 42501 with a hint naming the grant it wants. Tables that already existed keep their grants.
The safe fix is three things for that one table, in the same migration: grant only the privileges each role needs, enable Row Level Security, and add policies. The fix to avoid is the bulk one, grant ... on all tables in schema public: it re-exposes every table in public, and any table without RLS is then readable with the anon key that ships in your frontend.
What changed, per Supabase
Before: with default settings, every table created in public was granted to anon, authenticated and service_role, so it was reachable through the Data API the moment it existed, policies or not.
Timeline: opt-in at project creation from Apr 28, the default for new projects from May 30, and enforced on all existing projects on Oct 30, 2026.
Affected: anything that goes through the Data API, meaning supabase-js and the other client libraries, /rest/v1/ and /graphql/v1/. An app that only uses a direct Postgres connection (psql, an ORM, a server with a connection string) is not.
Only public changes. Tables in storage, auth, realtime and custom schemas keep their current grants and defaults.
It doesn't matter what created the table: the SQL editor, a migration, the Management API, MCP, the CLI or an AI coding tool.
RLS is unchanged. Without a grant, Postgres rejects the query before RLS comes into play, which is why you get an error instead of an empty array.
The safe fix, for one table
Read the error. The hint names the role and the privilege the request was missing:
{
"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;"
}
It is the minimum for that one request. Before you run it, decide whether that role should reach the table at all: a hint naming anon means a logged-out visitor asked.
Grant only what each role needs, on this table only. For a table signed-in users read and write and visitors never see:
grant select, insert, update, delete on public.your_table to authenticated;
-- only if server code reads it over the Data API with the secret key:
grant select, insert, update, delete on public.your_table to service_role;
Add grant select on public.your_table to anon; only if logged-out visitors should read it.
Enable Row Level Security and add policies in the same migration. Supabase's words: "Treat these three steps as a unit." Tables created in the Dashboard have RLS on by default; tables created in the SQL editor, a migration or another tool don't.
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 (auth.uid() = user_id);
Check what a stranger gets, with only the public key and no session, on a project you own:
For a table visitors shouldn't see, the 42501 error is the right answer. Rows mean the grant or the policy is wider than you meant.
The fixes to avoid
Bulk-granting the schema: grant select, insert, update, delete on all tables in schema public to anon, authenticated, service_role;. Supabase's changelog offers it to roll back an early opt-in, and then tells you to revoke private tables one by one. As a fix for one 42501, it re-exposes every table in public at once. Supabase's docs: tables exposed through the Data API without RLS "can be accessed by any role with matching grants", and that includes anon.
Restoring the old default privileges (alter default privileges ... grant ... on tables to anon, authenticated, service_role). That brings back the behavior Supabase removed: every future table reachable before anyone has written its policies.
Turning RLS off to make the error go away. The error is about the grant, not RLS. With a grant and RLS off, the role reads every row.
Switching the client to the secret (service_role) key. It bypasses RLS, and in a browser bundle it is everyone's.
If an AI agent creates your tables
Supabase's advice is to update the agent's system prompt or adopt the Supabase agent skill, which includes the grants step, so grants ship in the same migration as RLS and policies.
Agents can self-correct from the hint. Check which statement yours picked: a single-table grant to one role is what the hint asks for; a schema-wide grant is the fix to avoid.
The Data API exposure badge in the Table Editor and the Security Advisor list the tables worth reviewing. To see what new tables get by default, Supabase's securing-your-API guide has a pg_default_acl query; no rows means nothing until you grant it.
Ship Check Kit · $29
Rather not run it by hand?
The Ship Check Kit has your coding agent check the other side of this: SC-RLS-02 probes what the public key can actually read, so a broad grant on a table without RLS shows up as a failed check with the evidence and the fix. One of 44 launch checks.