Abbi Labs

Guides

Supabase "42501 permission denied for table": fix it with a GRANT, not by turning off RLS

By Pritesh Ghodge, security engineer · Last updated 30 October 2026

Fixed the error? Check you didn't open a table to everyone.

The Abbi Labs quick check uses only your public anon key to test whether common Supabase tables return rows, and also looks for secret keys in your JavaScript, exposed .env and .git files, and missing security headers. No sign-up.

No sign-up. Read-only GET requests to what your site already serves publicly. Scan only sites you own or are allowed to test.

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.

If a feature that worked yesterday now fails with this, you're in the right place:

{
  "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's not a bug and nothing was hacked. Supabase changed a default, and on 30 October 2026 it reached existing projects. The fix is a few lines of SQL. The risk is that the quickest-looking fixes (turning off RLS, or granting everything to everyone) leave your data readable by anyone who visits your site.

What changed

Supabase's Data API (what supabase-js, /rest/v1/ and GraphQL use) can only reach a table if the Postgres role making the request has been granted access to it. Until now, every new table in the public schema got select, insert, update, delete for the anon, authenticated and service_role roles automatically. Now it doesn't:

Supabase's reasoning is the same thing we see when scanning AI-built apps: agents and AI tools create tables with nobody reviewing the diff, and the old default exposed each one the moment it existed, before anyone had added RLS. Source: Supabase changelog: Tables not exposed to Data and GraphQL API automatically.

The two fixes not to use

1. Turning off RLS

It doesn't even fix the error: Postgres checks the grant first, so a missing grant fails with RLS on or off. But it's often what an AI assistant suggests when it sees "permission denied", and once someone also adds the grant, a table with RLS off is readable by anyone holding your anon key. That key is in your site's JavaScript by design.

2. Granting everything to everyone

-- Don't paste this to make the error go away
grant select, insert, update, delete on all tables in schema public to anon, authenticated, service_role;

This puts back the old behaviour for every table at once, including tables you meant to keep private (admin notes, internal logs, anything with emails). Each of them is then only as safe as its RLS policies. Supabase does document it, as the rollback for a project that opted in early and broke; it's not the fix for one missing table.

The safe fix: grant, enable RLS, add a policy, as one unit

Decide who actually needs the table. Logged-out visitors (anon) usually need little or nothing; signed-in users (authenticated) usually need their own rows.

-- 1. Grant only what each role needs
grant select, insert, update, delete on public.notes to authenticated;
grant select, insert, update, delete on public.notes to service_role;
-- (only if logged-out visitors must read it, e.g. a public catalogue)
-- grant select on public.notes to anon;

-- 2. Make sure RLS is on
alter table public.notes enable row level security;

-- 3. Say which rows each user may see and change
create policy "read own notes" on public.notes
  for select to authenticated using ((select auth.uid()) = user_id);
create policy "add own notes" on public.notes
  for insert to authenticated with check ((select auth.uid()) = user_id);

Replace notes and user_id with your table and its owner column. Add update/delete policies only if the app updates or deletes. If the error names a sequence rather than a table (older serial id columns), the role also needs grant usage, select on sequence public.notes_id_seq to authenticated;.

Put these statements in the same migration that creates the table, so the grant, RLS and policies always ship together.

A prompt for Lovable, Bolt or Cursor

AI builders tend to fix "permission denied" by widening access. Tell them what you want instead:

My Supabase query fails with 42501 "permission denied for table X".
Supabase no longer grants new public tables to API roles automatically.
Fix it safely:
1. Write a migration that grants only the privileges this app needs on X,
   per role (authenticated for signed-in features; anon only if logged-out
   visitors must read it - tell me why if you think they do).
2. Keep row level security enabled on X. Do not disable RLS.
3. Add policies so users can only read and change their own rows.
4. Do not grant on "all tables in schema public".
Show me the SQL before running it.

Supabase also says its agent skill includes the grants step, if your tool supports skills.

Check it worked, from the outside

Test as a stranger would, with only the anon key from your site:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/notes?select=*&limit=1" \
  -H "apikey: YOUR_ANON_KEY" -H "Authorization: Bearer YOUR_ANON_KEY"

Then do the two-account test: sign up as two users, create data as user A, and check user B can't see or change it. No outside scanner can do that one for you. To review every grant and policy at once, including INSERT and UPDATE checks a read test can't see, paste one query's output into the free Supabase RLS policy checker (runs in your browser). More on reading these results: how to check if your Supabase tables are readable by anyone.

Use the change to tidy up

The Table Editor shows a Data API exposure badge, and Security Advisor lists tables worth reviewing. If an older table doesn't need to be reachable from the browser at all, remove its API grants:

revoke all on table public.internal_logs from anon, authenticated;

Only do this for tables the frontend doesn't use, and test the app afterwards. Keep service_role if your edge functions or server use it.

Keep it that way

Every new table, view or quick fix from an AI edit is a chance for access to widen again. Abbi Labs checks your live app with the public key every visitor has, and Monitor re-scans weekly and after every deploy, emailing you only when something new appears.

Run a free scan

FAQ

Why am I suddenly getting 42501 permission denied for table in Supabase?

Supabase changed a default. On 30 October 2026 it applied to existing projects: new tables created in the public schema no longer get automatic select/insert/update/delete grants for the anon, authenticated and service_role roles, so the Data API (supabase-js, /rest/v1, GraphQL) can't see them until you grant access. Tables that existed before the change keep their grants. If your app or AI builder created a table after the change, that's the one returning 42501.

Will disabling RLS fix the 42501 error?

No. Grants are checked before Row Level Security, so with the grant missing the query fails whether RLS is on or off. The danger is the next step: if you disable RLS and then add the grant, every row in that table is readable (and possibly writable) by anyone holding your public anon key, which is in your website's JavaScript.

Is it safe to run grant all on all tables in schema public to anon?

Usually not. It undoes the protection for every table at once, including ones you meant to keep private, and whether those tables are then exposed depends entirely on each one having RLS and correct policies. Grant per table, per role, only what the app needs.

Does this change affect my existing tables?

Supabase says existing tables keep their current grants and stay reachable. It also doesn't affect the storage, auth or realtime schemas, or apps that only talk to Postgres over a direct connection (psql, an ORM or a server with a connection string).

Does Abbi Labs check this?

The free quick check on the home page (no sign-up) finds your Supabase URL and public key in your site's JavaScript, then asks the Data API for one row from a list of common table names (users, profiles, orders and similar) using only that public key. It flags any table that returns rows and stores only a yes/no, never the data. A table without a grant returns 42501 and is not reported. It can't see tables with unusual names or test what a logged-in user can read across accounts; the two-account test below covers that.

Related guides

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.