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
- You (or your AI builder) just added a feature with a new table, and that page fails while older pages still work.
- The browser console or network tab shows an error with code
42501and a message likepermission denied for table your_table. - The page just looks empty, or saving does nothing. Many AI-built apps hide database errors, so a refused request can look like "no data yet".
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:
- 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) andservice_role(your server code). - 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:
- Don't disable RLS to make the error go away. With the grant in place and RLS off, anyone with your public key can read the whole table.
- Don't grant everything on every table (for example
grant all on all tables in schema public to anon). That undoes the protection for tables you meant to keep private. - Don't give
anoninsert, update or delete unless logged-out visitors really need to write to that table, and then only with a strict policy.
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?
- Put the grants in the same migration that creates the table, next to
enable row level securityand the policies, so they can't be forgotten. - Read the
hintin the 42501 response: it names the role and the missing permission. Grant that, not more. - In the Supabase dashboard, the Table Editor shows a Data API exposure badge and the Security Advisor lists tables worth reviewing, per Supabase's announcement.
- In local development, recent Supabase CLI versions apply the same new default, so a migration that works locally with grants will behave the same in production.
Check that it worked
- 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.
- Log out and try the same page. Logged-out visitors should see nothing private.
- 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>" - 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 checkSettings 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.