Guides

My new Supabase table says "permission denied" (error 42501) or shows no data

Short answer: Supabase changed a default. New tables in the public schema are no longer reachable through the Data API (what supabase-js uses) until you grant access to them. Supabase says this applies to all existing projects from October 30, 2026, and it's already the default for projects created since late May. Tables you already have keep working. The fix is to grant only what each role needs on that one table, keep row-level security (RLS) on, and add policies. Don't turn RLS off, and don't grant everything to everyone.

Signs this is your problem

Supabase's own example of the error looks like this, and the hint tells you which role is missing which permission:

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

Why it happens

Supabase has two separate layers of protection:

  1. Grants decide whether a role can touch a table at all. The roles that matter are anon (visitors who aren't logged in), authenticated (logged-in users) and service_role (your server code).
  2. RLS policies decide which rows that role can see or change.

Until now, every new table in public got grants for all three roles automatically. Supabase is ending that so a new table isn't exposed before anyone has secured it. Its timeline: optional from April 28, 2026; the default for new projects from May 30, 2026 (rolled out gradually); applied to all existing projects on October 30, 2026. After that, a table created without a grant isn't visible to the Data API, however it was made: the SQL editor, a migration, the CLI or an AI coding tool.

It only matters if your app talks to the database through the Data API (supabase-js, the other client libraries, or /rest/v1/ and /graphql/v1). Apps that only use a direct Postgres connection aren't affected. Supabase also says tables in the auth, storage and realtime schemas, and tables you already have, keep their current grants.

The fix: grant one table, keep RLS on, add policies

Do these three things together for each new table your app reads or writes. Replace your_table and user_id with your own names, and only grant what each role really needs:

-- 1. Grant only what each role needs on THIS table
grant select, insert, update, delete on public.your_table to authenticated;
grant select, insert, update, delete on public.your_table to service_role;
-- Only if logged-out visitors should see this table (e.g. a public list):
-- grant select on public.your_table to anon;

-- 2. Keep row-level security on
alter table public.your_table enable row level security;

-- 3. Policies: people only see and change their own rows
create policy "read own rows" on public.your_table
  for select to authenticated using (auth.uid() = user_id);
create policy "add own rows" on public.your_table
  for insert to authenticated with check (auth.uid() = user_id);
create policy "edit own rows" on public.your_table
  for update to authenticated using (auth.uid() = user_id) with check (auth.uid() = user_id);

What not to do, even if a forum answer or an AI suggests it:

If inserts still fail with permission denied for sequence, the table uses an older-style auto-numbering column. Grant usage, select on that one sequence to the same role.

Copy-paste prompt for Lovable, Bolt, Cursor or another AI builder

Supabase no longer exposes new tables in the public schema to the Data API until they are granted (applies to existing projects from Oct 30, 2026). My app gets "permission denied" (42501) or empty results on a newer table. Please:
1. List every table my app reads or writes through supabase-js, and which ones were created recently.
2. For each of those, write ONE migration that grants only the privileges each role needs on that table (authenticated and service_role as needed; anon only select, and only if logged-out visitors must see it), keeps row level security enabled, and adds policies so users only see and change their own rows (auth.uid() = user_id, with check on insert and update).
3. Do NOT disable RLS and do NOT grant all privileges to anon or on all tables.
4. Show me the SQL before running it. Then make the app show a real error message instead of an empty page when a database request fails.
5. From now on, every new table you create must include its grants, RLS and policies in the same migration.

Coding by hand?

Check that it worked

  1. In a private window, log in as a normal test user (not an admin) and use the feature. Rows you create should show up, and you should see only your own.
  2. Log out and try the same page. Logged-out visitors should see nothing private.
  3. On your own app only, ask for the table the way a stranger would. A logged-out request for a private table should return [] (granted, but RLS blocks it) or a permission error, never real rows:
    curl "https://<your-project>.supabase.co/rest/v1/your_table?select=*&limit=1" \
      -H "apikey: <your anon key>"
  4. An empty result for a logged-in user who should see rows usually means the grant is fine but a policy is missing or wrong, not that the grant is missing.

How AppFirstAid helps

Our free check never touches your database. When it sees your site's code querying Supabase tables, it shows an info-level heads-up about this change, with the tables it spotted, so you know what to review. For apps whose owner has verified them, Monitor scans go one step further: they send header-only requests (no rows are read) to those tables with your public key and flag any that answer with the 42501 "permission denied" error. A journey test signs up and uses your app like a real customer, which is the surest way to catch a page that has quietly gone empty. For a wider list of what scans can and can't see, read What built-in security scans miss.

Sources, read October 26, 2026: Supabase changelog "Breaking Change: Tables not exposed to Data and GraphQL API automatically" (dates, the 42501 example, the grant + RLS + policy steps, who is affected) and Supabase docs "Securing your API" (default privileges and explicit grants). Supabase may change these details.

Check your live app in under a minute

AppFirstAid's free check looks at your live site from the outside: leaked keys, files left in public, safety settings, and whether your sign-up, login and checkout pages open. No signup. Journey tests that sign up, log in and try checkout like a real customer are coming soon (email us to hear when).

Run the free check

Settings move around as these tools update. If a menu name has changed, look for the same words nearby. Spotted something out of date? Tell us.

More guides