Supabase 42501 permission denied for table
· PlugLeak
PostgREST error 42501 permission denied for table means a new public table has no GRANT. Disabling RLS does not fix it. Here is the grant, RLS policy, and what a passive scan cannot see.
A Lovable, Bolt, or Cursor app calls Supabase from the browser with supabase-js. The table is in the Table Editor. The request comes back 42501, permission denied for table your_table, and the hint looks like GRANT SELECT ON public.your_table TO anon;. Sometimes the new table never appears on the Data API at all. The table is not missing. Postgres has it. The Data API is not allowed to use it yet.
Supabase documented this as a breaking change: new tables in the public schema are no longer exposed to the Data API (PostgREST and the client libraries, including supabase-js) or the GraphQL API until you GRANT them (changelog). The rollout in that post is April 28, 2026 for an opt-in at project creation, May 30, 2026 as the default for new projects (a gradual rollout over a few weeks), and October 30, 2026 enforced on all existing projects. Existing tables keep the grants they already have. This page is the fix when an AI tool created the table and the app broke.
What the error is
PostgREST returns this when the role on the request has no privilege on the table. Supabase prints the same body in the changelog and in Securing your API:
{
"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;"
}The hint is specific. It names the role that made the request and the privilege that role lacks, plus a GRANT that would add it. A signed-out request uses the anon role. A signed-in Supabase Auth user uses authenticated. Read the role in your hint. Do not paste the sample TO anon onto a table that only signed-in users should touch (Row Level Security).
The same changelog says that without a grant the API cannot see the table, no matter how you created it: SQL editor, migrations, Management API, MCP, CLI, or an AI coding tool. That is the "new table not showing in the API" symptom. The table is in Postgres. PostgREST and GraphQL do not expose it until a role they use has a privilege. supabase-js talks to that API. It does not open a direct Postgres connection.
A direct connection is a different path. The changelog says psql, an ORM, or an app server using the connection string is not affected. A table you can open in the dashboard can still be invisible to supabase-js until the Data API role has a grant.
Disabling RLS does not fix 42501
Grants and Row Level Security are two checks. The changelog states it directly: grants control whether a role can access a table at all, and RLS controls which rows that role can see. RLS behavior did not change in this rollout. The RLS guide says Postgres runs the grant check first, and a missing grant raises 42501 before any policy runs. Turning RLS off does not create a privilege. The role still cannot touch the table, so the same permission denied for table remains.
Turning RLS off is also the wrong direction once a grant exists. Supabase's warning on that page: a table in an exposed schema without RLS is readable and writable by any role with a grant on it. public is exposed by the Data API by default. Enable RLS on every table you expose. Policies then decide the rows. The Supabase RLS checklist is the policy SQL and the signed-out curl test. This page stops at the grant that has to exist before those policies run.
42501 is not only a missing grant. The Database API 42501 errors page says an INSERT or UPDATE that fails the RLS check also returns 42501, and so does a query that touches a column the role is not allowed to use, including select *. Use the hint. A hint that prints a GRANT for the table is the missing-grant case below. A failure that names a policy is a row rule, which the checklist covers. Neither case is fixed by disabling RLS.
The three statements to run together
Supabase says to treat the grant, enable row level security, and the policy as one unit, in the same migration. If the grant is missing, Postgres rejects the query before RLS comes into play. This is that unit, with the grants narrowed to what a private table usually needs. Change your_table and user_id to your names. The changelog's sample also grants select to anon. Leave that line out unless signed-out visitors are supposed to read the table.
-- 1. Grant only the privileges the role needs.
-- Uncomment the anon line only when the table is public to read.
-- grant select on public.your_table to anon;
grant select, insert, update, delete on public.your_table to authenticated;
grant select, insert, update, delete on public.your_table to service_role;
-- 2. Enable RLS
alter table public.your_table enable row level security;
-- 3. Add the policies you need
create policy "users can read their own rows"
on public.your_table
for select
to authenticated
using (auth.uid() = user_id);What each grant is for, from Securing your API and the RLS guide:
anonis the signed-out role.GRANT SELECTtoanononly if the table must be publicly readable. Pair it with a policy you mean to be public. A policy ofto anon using (true)lets every signed-out visitor read every row that role can already reach.authenticatedis the signed-in role. Grantselect,insert,update, anddeleteonly for the operations the app actually performs. Then add a policy for each of those operations. The block above shows theselectpolicy from the changelog. Postgres does not accept several operations in oneforclause, so insert, update, and delete each need their own policy. The RLS checklist has those four.service_roleis for server-side code. The changelog includes it in the same unit. That role bypasses RLS, so the key that uses it stays off the browser. Do not "fix" 42501 by pointing the Lovable, Bolt, or Cursor client at theservice_rolekey. That mistake is the service_role exposure guide.
After enable row level security, Supabase says no data is accessible through the API with a publishable key until you create policies. An empty result at that point means the grant worked and the policy is what is left. It is not a reason to grant more privileges or to turn RLS off.
The RLS guide's examples write the comparison as (select auth.uid()) = user_id. The changelog sample uses auth.uid() = user_id. Keep the column name your table actually uses.
For a table anyone may read and nobody may change from the client, the RLS guide's public-read pattern is the one to copy: GRANT SELECT to anon and authenticated, RLS on, and a select policy with using (true). Do not also grant insert, update, or delete to anon on that table.
Do not GRANT ALL to anon to make the error go away
grant all on table public.your_table to anon, or a bulk grant ... on all tables in schema public to anon, will clear a missing-grant error. It also hands anon the privileges on every table included, including tables you have not written policies for. Supabase says those grants make the table reachable through the Data API, and a table in an exposed schema without RLS is then readable and writable by that role. The changelog's own rollback uses a bulk grant to restore the old defaults. That block is a rollback, not the fix for one new table. Run the grants for the one table, then look at what else is exposed.
Two screens are the official review, both named in the changelog. The Data API exposure badge in the Table Editor and the Security Advisor list the tables worth reviewing. The changelog also says that from the announcement through October 30, 2026, the Security Advisor flags affected tables and shows the remediation SQL. Securing your API tells you to turn off "Automatically expose new tables" in the Data API settings when you want that opt-in. The 42501 troubleshooting page says you can change the privileges in the dashboard under Integrations → Data API, or with SQL. You can see the privileges already on a table with the query from that page:
select grantee, privilege_type
from information_schema.role_table_grants
where table_name = 'your_table';anon should appear only for a privilege a signed-out visitor is supposed to have. Securing your API says the Data API does not use truncate, references, trigger, or maintain. The RLS checklist has a query limited to anon and authenticated if you want that narrower list.
Put the GRANT in the migration the AI writes
The changelog's instruction for AI coding tools: update the system prompt, or adopt the Supabase agent skill, which includes the grants step and which Supabase keeps updated as the platform changes. Agents can also self-correct from the PostgREST hint. If you run your own migrations, bundle the GRANT with enable row level security and the policies in the same file, so the next environment does not create a silent table. The skill is announced in Supabase Agent Skills.
A prompt that matches those docs, for Lovable, Bolt, or Cursor:
This Supabase table returns PostgREST 42501 "permission denied for table".
Do not disable RLS. Do not GRANT ALL to anon. Do not put the service_role key in client code.
1) In the same migration as the table, GRANT only the privileges each role needs.
SELECT to anon only if signed-out visitors must read it.
SELECT, INSERT, UPDATE, DELETE to authenticated only for operations the app uses.
The same table privileges to service_role for server-side code.
2) ALTER TABLE ... ENABLE ROW LEVEL SECURITY.
3) Add one policy per operation so each user only reaches their own rows (auth.uid() = user_id).
List every file you changed.Seeing the project URL and the anon key in the network tab is a separate question. That key is supposed to ship in the browser. The 42501 is the grant, not the key. How to tell that key from a secret is the Bolt Supabase API key guide.
What this change does not touch
- Tables that already existed keep their current grants and stay reachable. A page that worked before October 30, 2026 and never gained a new table is not what this error is describing.
- Direct Postgres (
psql, an ORM, a connection string) does not use the Data API, so this default does not apply there. - The changelog's FAQ says
storage,auth,realtime, and custom schemas keep their current grants and their current defaults. The change is the default privileges on new objects inpublic. - The same default-privilege change revokes automatic
usageandselecton new sequences inpublic. If the hint names a sequence instead of a table, run theGRANTthe hint prints. Do not answer it by granting every privilege toanon.
Securing your API still describes the older default on existing projects: new tables in public receiving select, insert, update, and delete for anon, authenticated, and service_role. It also says those defaults differ by when the project was created and by the Data API setting, and that Supabase is changing the platform default so exposure is opt-in. If your new table returns the hint above, the privilege is missing now, whichever sentence you read first. Grant the one privilege, then enable RLS.
What a passive scan can tell you
PlugLeak does not connect to your database, so it cannot see grants, policies, or this 42501. The free scan at plugleak.com/#scan fetches the live page and the same-site JavaScript a visitor already downloads. It looks for exposed keys and secrets, including a service_role JWT or an sb_secret_ key in that bundle, for a public /.env or /.git/config, and for security headers and the TLS certificate. It does not log in and it does not test RLS. Run it after you change the client, so a "fix" that moved the secret key into the browser does not ship. Then confirm the grant in the SQL above. A clean scan is an automated check of that public bundle, not a review of the table.
FAQ
Why does Supabase return 42501 permission denied for table?
The changelog says a missing grant makes PostgREST return code 42501, message "permission denied for table your_table", with a hint such as "GRANT SELECT ON public.your_table TO anon;". The hint names the role and the privilege that are missing. On October 30, 2026 that behavior was enforced on existing projects: a new table in the public schema is not exposed to the Data API or GraphQL until you GRANT it. Tables that already existed keep their current grants. A direct Postgres connection is not affected.
Does disabling RLS fix Supabase error 42501?
No. Supabase says grants and Row Level Security are separate layers. Grants decide whether a role can access the table at all. RLS decides which rows that role can see. A missing grant raises 42501 before any policy runs, so turning RLS off does not add the privilege. The RLS docs also warn that a table in an exposed schema without RLS is readable and writable by any role that already has a grant.
What should I GRANT so the new table shows up in the API?
Grant SELECT to anon only when signed-out visitors are supposed to read the table. Grant SELECT, INSERT, UPDATE, and DELETE to authenticated only for the operations the signed-in app uses. Then enable RLS and add a policy for each of those operations. Do not run GRANT ALL to anon, and do not bulk-grant every table in public to anon, just to clear the error. The changelog says that without a grant the Data API cannot see the table. With a grant and RLS off, the role can reach every row.
Will the free PlugLeak scan catch a missing Supabase grant or a bad RLS policy?
No. The free scan at https://plugleak.com/#scan fetches the live page and same-site JavaScript. It looks for exposed keys and secrets, including a service_role key in the bundle, for public /.env and /.git/config files, and for security headers and TLS. It does not query the database, test Row Level Security, or check grants.
Sources
Check your app now — free, passive, about 20 seconds.
Same scanner as the homepage scan. Paste the live URL. It reads what a visitor already downloads. It does not log in or query your database.