Guide

Supabase 42501 “permission denied for table” after 30 October: the GRANT fix

Last checked 21 Oct 2026 · 5 min read · by Ship Weekend

Short answer: on 30 October 2026 Supabase applies a new default to all existing projects: new tables you create in the public schema are no longer reachable through the Data API (supabase-js, REST, GraphQL) until you add an explicit Postgres GRANT.¹ Tables you already have keep their current grants and keep working.¹ If your AI builder adds a table after that date and your app suddenly gets 42501 permission denied for table ..., add a narrow grant for that table, make sure RLS is on, and write policies. Don't paste a blanket grant to anon.

Who this affects

Per Supabase's changelog, you need to act if your app reads or writes public tables through the Data API (which is what supabase-js does), and your migrations, CLI scripts or AI coding tool create tables without explicit GRANT statements.¹ That describes most Lovable, Bolt and Cursor apps that use Supabase.

You're unaffected if you only talk to Postgres over a direct connection (psql, an ORM, or a server with a connection string).¹ Tables in the auth, storage, realtime and custom schemas keep their current grants and defaults.¹

Timeline from the changelog:¹

Date What happens
28 Apr 2026 Opt-in available when creating a new project
30 May 2026 New behaviour becomes the default for new projects (gradual rollout)
23 Oct 2026 Supabase's final email notice to project owners and admins
30 Oct 2026 Enforced on all existing projects. New public tables without an explicit grant aren't reachable from the Data API

42501 or an empty array? Tell grants and RLS apart

These two look similar in a broken app, but they have different fixes:

What you see What it means Fix
Error 42501 with "permission denied for table your_table" The role (anon or authenticated) has no GRANT on the table. Postgres rejects the query before RLS is even checked.¹ Add the grant (below).
An empty array [] and no error on a select The grant is fine, but RLS is on and no policy matches this user. Once RLS is on, nothing is readable with the publishable key until you create policies.³ Add or fix a select policy. Don't touch grants.
Error 42501 with "new row violates row-level security policy" on an insert or update Grant is fine; RLS blocked the write.² Add or fix the insert / update policy (its with check).

So: 42501 "permission denied" = missing GRANT. Empty array = RLS on with no matching policy. Read the error message before you change anything. The hint in the error even names the role and privilege that's missing.¹ Supabase notes these errors often show up in clients as 401 or 403.²

{
  "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 fix: one migration per new table

Grants decide whether a role can touch the table at all; RLS decides which rows it sees.¹ Supabase says to treat grant, RLS and policies as one unit.¹ Replace your_table with your table name and grant only what each role needs:

-- 1. Grants: only the privileges each role actually needs
grant select, insert, update, delete on table public.your_table to authenticated;
-- only if your Edge Functions or server code use the service_role key on this table:
grant select, insert, update, delete on table public.your_table to service_role;

-- 2. Turn on Row Level Security
alter table public.your_table enable row level security;

-- 3. Policies: one per operation, tied to the signed-in user
create policy "owners can read their rows" on public.your_table
  for select to authenticated using ((select auth.uid()) = user_id);

create policy "owners can add their rows" on public.your_table
  for insert to authenticated with check ((select auth.uid()) = user_id);

create policy "owners can update their rows" on public.your_table
  for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);

create policy "owners can delete their rows" on public.your_table
  for delete to authenticated using ((select auth.uid()) = user_id);

Signed-out visitors need it too? Only for genuinely public data (a product catalogue, published posts), and usually read-only:

grant select on table public.your_table to anon;
create policy "anyone can read published rows" on public.your_table
  for select to anon using (published = true);

If inserts then fail with "permission denied for sequence", the table uses an old-style serial id. Grant the sequence too: grant usage, select on sequence public.your_table_id_seq to authenticated; (Supabase's own opt-in script covers sequences alongside tables.¹)

Warning: never grant to anon on a table without RLS. Your publishable (anon) key is public: anyone can read it from your site's JavaScript. A grant to anon on a table with RLS off lets anyone on the internet read, and with insert/update/delete change, every row. Always run enable row level security in the same migration as the grant. Also don't paste Supabase's bulk rollback (grant ... on all tables in schema public to anon, authenticated, service_role) as a quick fix: it re-exposes every table, including ones you meant to keep private.¹

Prompt to give your AI builder (Lovable, Bolt, Cursor): "Whenever you create a table in Supabase, in the same migration: add explicit GRANTs for only the roles that need it (no grant to anon unless signed-out users must read it), enable Row Level Security, and create a policy for each operation using auth.uid(). Never grant to anon on a table without RLS."

Check what a table currently grants

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

(Adapted from Supabase's 42501 troubleshooting guide.²) If anon or authenticated is missing the privilege your app uses, that's your 42501.

Before 30 October

FAQ

Will my existing tables break on 30 October? No. Per Supabase, existing tables keep their current grants and stay reachable. The change affects new tables created in the public schema after it reaches your project.

Why does my query return an empty array instead of an error? That's Row Level Security, not grants: RLS is on and no policy matches the current user, so Postgres returns no rows. Add or fix a select policy. A 42501 "permission denied for table" error means a GRANT is missing.

Is it safe to grant select to anon? Only on tables that have RLS enabled and a policy that limits rows to what's genuinely public. Never grant to anon on a table without RLS, because the anon (publishable) key is visible to everyone.


¹ Supabase changelog, "Breaking Change: Tables not exposed to Data and GraphQL API automatically" (checked 21 Oct 2026): https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically ² 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