Skip to content

Guides · VibeVigil

Supabase "permission denied for table" (Error 42501): What Changed on October 30 and How to Fix It Safely

Your Lovable, Bolt, v0 or Cursor app made a new Supabase table and now it fails with 42501 "permission denied for table". Here's why it happens, how to fix it with the right GRANT and RLS policy, and the risky fixes to avoid.

You asked your AI tool for a new feature. It made a new table in Supabase. Now the app shows an error like this:

{
  "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;"
}

This guide explains why, and how to fix it without opening your data to everyone. You don't need to be a developer.

The short answer

  • Supabase changed a default. Starting October 30, 2026, it applies to all existing projects: new tables in the public schema are no longer reachable through the Data API until you add a GRANT. (Supabase changelog)
  • Your old tables keep working. Only new tables are affected.
  • The fix is to grant only the access each role needs, and to turn on Row Level Security (RLS) with policies in the same step.
  • Do not fix it by turning RLS off or by granting everything to everyone.

Why this happens

Supabase has two separate locks on each table:

  1. Grants decide if a role (like anon for logged-out visitors, or authenticated for signed-in users) can touch the table at all.
  2. RLS policies decide which rows that role can see or change.

Before, Supabase gave every new table in public full grants to anon, authenticated and service_role right away. So every new table was reachable from the browser the moment it was made, even if you didn't mean it to be. (Supabase: Securing your API)

Now those automatic grants are gone for new tables. That is safer. But AI tools that expect the old behavior will make a table, try to use it, and hit 42501.

Supabase says this change matters if your app uses supabase-js or other client libraries (this is how most Lovable, Bolt and v0 apps talk to Supabase), or if your migrations or AI tool create tables without GRANT statements. If your app only connects to Postgres directly from a server (with a connection string or an ORM), it is not affected. (Supabase changelog)

Step 1: Find out which kind of 42501 you have

Error 42501 can mean a few different things. Look at the full error message.

What you seeWhat it usually means
permission denied for table ... with a GRANT hintThe table is missing a grant (the October 30 change)
new row violates row-level security policyGrants are fine, but no RLS policy allows this insert or update
42501 only when you use select * or certain columnsColumn-level limits block some columns

Supabase lists these causes in its 42501 troubleshooting guide. This guide focuses on the first one. If you see the second one, skip to Step 4.

Step 2: Decide who should use the new table

Before you add anything, answer one question per table: who needs this, and what do they need to do?

Table exampleLogged-out visitors (anon)Signed-in users (authenticated)
blog_posts (public content)Read onlyRead only
profilesNoneRead and edit their own row
orders, messages, notesNoneRead and edit their own rows
admin_settingsNoneNone (server only)

A simple rule: if a logged-out stranger has no reason to see it, don't grant anything to anon.

Step 3: Add the grant and RLS in the same step

Open the SQL Editor in your Supabase dashboard. Here is a safe pattern for a private table where each user owns their own rows (swap in your table name):

-- 1. Turn on RLS first, so nothing leaks while you set it up
alter table public.notes enable row level security;

-- 2. Signed-in users can use the table (RLS still limits the rows)
grant select, insert, update, delete on table public.notes to authenticated;

-- 3. Your server code can use it too
grant select, insert, update, delete on table public.notes to service_role;

-- 4. Policies: each user only reaches their own rows
create policy "Users read own notes"
  on public.notes for select to authenticated
  using ((select auth.uid()) = user_id);

create policy "Users add own notes"
  on public.notes for insert to authenticated
  with check ((select auth.uid()) = user_id);

create policy "Users edit 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);

Notice there is no grant to anon. Logged-out visitors can't reach this table at all.

For a table that really is public to read (like blog_posts), you can grant read-only access to anon:

alter table public.blog_posts enable row level security;
grant select on table public.blog_posts to anon, authenticated;

create policy "Anyone can read published posts"
  on public.blog_posts for select to anon, authenticated
  using (published = true);

Supabase recommends putting grants and RLS together in the same migration. (Supabase: Securing your API)

Step 4: If the error says "new row violates row-level security policy"

This means the grant worked, but no policy lets this user save the row. Common causes:

  • There is a select policy but no insert policy (or no with check).
  • The user is not signed in when they save.
  • The app saves the row without a user_id, or with the wrong one.

Fix the policy or the app code. Don't turn RLS off to make the error go away.

Step 5: Test it like a stranger

  1. Open your app in a private / incognito window where you are not logged in.
  2. Try to load the page that uses the new table. Private data should not show up.
  3. Sign up as a second test user and check that you can't see the first user's rows.
  4. Open Advisors → Security Advisor in the Supabase dashboard and fix anything it flags.

Fixes to avoid

These make the error go away, but they can leave your data open:

  • ❌ alter table ... disable row level security;
  • ❌ grant all on all tables in schema public to anon;
  • ❌ A policy with using (true) on private data (this lets every user read every row)
  • ❌ Moving the service_role key into frontend code so the request "just works"

If your AI agent suggests one of these, stop and ask it to use the pattern in Step 3 instead.

A prompt you can paste into your coding agent

Copy this into Lovable, Bolt, v0, Cursor or your coding agent:

My Supabase app shows error 42501 "permission denied for table" on a new table.
Supabase no longer grants access to new tables in the public schema automatically.

Please:
1. List every table you created recently and who should use it
   (logged-out visitors, signed-in users, server only).
2. For each table, write ONE migration that:
   - enables row level security,
   - grants only the needed privileges (no grant to anon unless the data is meant to be public),
   - adds RLS policies so signed-in users can only read and change their own rows
     (use (select auth.uid()) = user_id, with WITH CHECK on insert and update).
3. Do NOT disable RLS, do NOT use "using (true)" on private data,
   and do NOT put the service_role key in frontend code.
4. Tell me how to test it as a logged-out visitor and as a second user.
From now on, include the grants and RLS policies every time you create a new table.

FAQ

Will my existing tables break on October 30? Supabase says no. Existing tables keep their current grants and stay reachable. Only tables created after the change reaches your project need explicit grants.

Is this change making my app less secure? No, it's safer. Before, every new table was reachable from the browser right away. Now you choose. The risk is "fixing" the error in a way that opens everything back up.

I granted access and now logged-out people can read my table. What went wrong? You probably granted to anon on a private table, and either RLS is off or a policy allows everyone. Remove the anon grant and check your policies. Our guide on checking Supabase RLS walks you through it.

Does this affect my storage buckets? No, this change is about tables (and functions) in the public schema. Files have their own rules. See how to make a Supabase storage bucket private.

My app talks to the database only from my server. Do I need to do anything? If you only use a direct Postgres connection (a connection string or an ORM), Supabase says this change does not affect you.

Related guides

Sources

  • Supabase changelog, Breaking change: tables not exposed to Data and GraphQL API automatically: https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically
  • Supabase docs, Securing your API (grants and default privileges): https://supabase.com/docs/guides/api/securing-your-api
  • Supabase docs, Database API 42501 errors: https://supabase.com/docs/guides/troubleshooting/database-api-42501-errors
  • Supabase docs, Row Level Security: https://supabase.com/docs/guides/database/postgres/row-level-security

Scan your app free. It checks the public page of a site you own. No account.

Supabase "permission denied for table" (Error 42501): What Changed on October 30 and How to Fix It Safely · VibeVigil