Skip to content
VibePatchers

Fix guide ·

Supabase “42501 permission denied for table” on new tables: the October 30 change for Lovable and Bolt apps

Your Lovable or Bolt app worked, you asked the AI for a new feature, and now it fails with permission denied for table. If the table is new, the likely cause is a Supabase platform change, not your code. On October 30, 2026, Supabase stops automatically granting Data API access to new tables in the public schema on every existing project. Here’s what changed, how to spot it, and the fix Supabase recommends, without opening up more than you need.

What changed, and when

Until now, Supabase granted select, insert, update and delete on every new table in public to the anon, authenticated and service_role roles, so a table was reachable from supabase-js the moment it existed. According to Supabase’s changelog:

  • From May 30, 2026 (rolled out over a few weeks), new projects were created without those default grants.
  • On October 30, 2026, the same setting is applied to all existing projects.

After that, a new table in public needs an explicit GRANT before the Data API (REST, GraphQL and the client libraries) can see it. Supabase’s opt-in statements revoke the default privileges on new sequences too.

What doesn’t change:

  • Existing tables keep their grants and stay reachable.
  • RLS works the same. Grants and RLS are separate layers: the grant decides whether a role can reach the table at all, and RLS decides which rows it sees.
  • Direct Postgres connections (psql, an ORM, a server with a connection string) aren’t affected.
  • Tables in storage, auth, realtime and custom schemas keep their current grants and defaults.

Who is affected

Mostly apps that keep growing after launch. Lovable and Bolt apps use supabase-js, which goes through the Data API. Ask for a new feature (“let users save favorites”) and the AI usually writes a migration that creates a table. Without GRANT statements in that migration, the new table won’t show up in the API after October 30. Projects created after late May may already work this way.

One quiet case: if the AI “rebuilds” a table by dropping and recreating it, Postgres treats it as a brand-new table, and it starts with no grants either.

How to recognize it

Supabase documents this response for a missing grant:

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

In the app it looks like an empty list, a save that fails, or a “permission denied” toast; a new table not showing in the API and a Lovable app hitting permission denied after October 30 are usually this same problem. In the network tab, the request to /rest/v1/your_table returns that body. Supabase’s 42501 troubleshooting page notes clients often report it as a 401 or 403.

Watch the message text, because RLS can also return code 42501: new row violates row-level security policy means the grant exists and a policy is blocking the write. That’s an RLS fix, covered in How to fix Supabase RLS in a Lovable app.

To confirm, run this in the SQL Editor:

select grantee, privilege_type
from information_schema.role_table_grants
where table_schema = 'public' and table_name = 'your_table'
order by grantee, privilege_type;

No rows for anon, authenticated or service_role means the grant is missing.

The fix: grant, enable RLS, add policies, as one unit

Supabase says to treat these three steps as a unit. Without the grant, Postgres rejects the query before RLS runs. With a grant but no RLS, any role that holds the grant can read and write every row (Supabase RLS docs).

A per-user table where signed-out visitors need nothing:

-- 1. Grant only what each role needs (no anon grant on purpose)
grant select, insert, update, delete on public.favorites to authenticated;
grant select, insert, update, delete on public.favorites to service_role;

-- 2. Enable RLS
alter table public.favorites enable row level security;

-- 3. One policy per operation, tied to the signed-in user
create policy "read own favorites" on public.favorites
  for select to authenticated using ((select auth.uid()) = user_id);
create policy "add own favorites" on public.favorites
  for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "update own favorites" on public.favorites
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);
create policy "delete own favorites" on public.favorites
  for delete to authenticated using ((select auth.uid()) = user_id);

Genuinely public data, such as published posts, gets select only:

grant select on public.posts to anon, authenticated;
grant select, insert, update, delete on public.posts to service_role;
alter table public.posts enable row level security;
create policy "anyone reads published posts" on public.posts
  for select to anon, authenticated using (published = true);

The hint names the role that was refused, not the role that should have access, so don’t grant to anon just because it says so. Keep these statements in a migration file.

Make Lovable or Bolt add grants with every new table

Supabase suggests updating your AI tool’s instructions so grants are part of table creation. Paste this into your project’s standing instructions, or at the top of the message that asks for a new feature:

Our Supabase project does not grant Data API access to new tables automatically.
Whenever you create a table or view in the public schema, put these in the same migration:
1. Explicit GRANT statements with only the privileges each role needs:
   nothing for anon unless the data is meant to be public (then SELECT only),
   only the operations the app performs for authenticated,
   and select/insert/update/delete for service_role if server code or edge functions use it.
2. ALTER TABLE ... ENABLE ROW LEVEL SECURITY.
3. One RLS policy per operation (select, insert, update, delete), with a TO clause,
   checking user-owned rows against (select auth.uid()).
Prefer identity columns. If you use serial, also GRANT USAGE on its sequence to roles that insert.
Never use GRANT ALL, never grant on ALL TABLES IN SCHEMA public, never change default privileges,
and never disable RLS to fix an error. If you see 42501 "permission denied for table",
add the missing grant for that one table and role.
After the migration, list the grants and policies you created.

Test as a signed-out visitor and as a signed-in user

The SQL Editor runs as a privileged role, so a query that works there proves nothing about your app.

Signed out. Call the API with only your publishable (anon) key:

curl "$SUPABASE_URL/rest/v1/favorites?select=*" \
  -H "apikey: $SUPABASE_PUBLISHABLE_KEY" \
  -H "Authorization: Bearer $SUPABASE_PUBLISHABLE_KEY"

For a private table with no anon grant, a 42501 error is the result you want. For a public table, you should get only the rows meant to be public.

Signed in. In a private window, sign in as a test user and use the new feature; the request should succeed. Then sign in as a second user and confirm you can’t see or change the first user’s rows. Or check it in SQL, as Supabase’s RLS docs do:

begin;
set local role authenticated;
set local request.jwt.claim.sub = '<test user id>';
select * from public.favorites;  -- only that user's rows
rollback;

The Security Advisor and the Data API exposure badge in the Table Editor also flag affected tables.

Gotchas

  • Edge functions get 42501 too. The secret (service_role) key bypasses RLS, but not grants, and the old defaults covered service_role as well. Server code that writes to a new table needs a service_role grant. Also, a secret key bypasses RLS only when the request carries no user access token; if your function forwards the user’s Authorization header, it runs as that user.
  • Policies on a table with no grant never run. Supabase’s docs say a missing grant raises 42501 “before any policy runs”. Check grants before you rewrite policies.
  • Serial ids need the sequence. Postgres requires USAGE on a sequence to call nextval (Postgres docs), so a serial id needs grant usage on sequence public.favorites_id_seq to authenticated;. Identity columns, which Supabase’s own examples use, don’t need this.
  • New views need grants, and views skip RLS by default. Create them with security_invoker = true (Postgres 15+) so they follow the underlying table’s policies.
  • Don’t fix it with a blanket grant. grant all on all tables in schema public to anon, authenticated, or restoring the old default privileges, makes the error go away but re-exposes every table, and any table without RLS becomes readable and writable with your public key. Supabase documents a bulk grant only as a rollback to the old behavior. Grant per table instead.

Sources and next steps

Sources: Supabase changelog: tables not exposed automatically, Creating API routes, Securing your API, Row Level Security.

Want an outside look first? The free app scanner checks a live URL for secret-looking keys and, if Supabase credentials are visible, whether tables can be read anonymously. For the tables you already have, see How to fix Supabase RLS in a Lovable app. If new features are already failing with 42501, a flat-fee Rescue ($149) adds the grants, RLS and policies, tests them as a visitor and as a signed-in user, and hands you a before/after verification report against our checklist. No fix, no charge.

Rather have it fixed and proven?

Our flat-fee Rescue fixes this and hands you a before/after verification report within 48 hours of working access.

See pricing