Supabase Table Not Showing Up in API (2026): The Grants Fix

You created a table in the Supabase dashboard, called .from('orders').select(), and got back nothing. No rows, no obvious crash, just an empty result and an error object your code probably swallowed. The table is right there in the table editor.

Two different failures produce that symptom, and they have opposite fixes. Log the actual error before you touch anything.

TL;DR

Since May 30, 2026, new Supabase projects stop granting anon, authenticated and service_role access to new public tables automatically. A missing grant returns 42501 permission denied for table, and the fix is an explicit GRANT. A missing table in the schema cache returns PGRST205, which is a schema or cache problem instead. Existing projects get the new behaviour on October 30, 2026. Do not fix it with GRANT ALL ON ALL TABLES IN SCHEMA public TO anon, which is the answer you'll find most often and the one that opens your whole database.

Read the error code first

The Supabase JavaScript client returns errors instead of throwing them, which is why so many people see "no data" and never see the reason. Print the whole object.

src/lib/orders.js
const { data, error } = await supabase.from('orders').select('*')
if (error) console.error(JSON.stringify(error, null, 2))

You'll get one of two shapes.

A grant is missing. Supabase documents this exact response:

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

PostgREST cannot see the table at all:

PGRST205 response
{
  "code": "PGRST205",
  "message": "Could not find the table 'public.your_table' in the schema cache"
}
CodeWhat it meansWhere the fix goes
42501Table found, role rejectedA GRANT in the SQL editor
PGRST205Table invisible to PostgRESTAPI settings, schema name, or a cache reload
42P01Relation does not existYour table name or schema is wrong

Everything below assumes 42501. If you got PGRST205, skip to the PGRST205 section.

What Supabase actually changed

Until this year, creating a table in public silently handed select, insert, update and delete to anon, authenticated and service_role. That default is why so many vibe-coded apps ship with an open database: the table was reachable the moment it existed, and nothing asked you to confirm that.

Supabase is reversing it. The rollout:

DateWhat happens
April 28, 2026Opt-in toggle appears at project creation
May 18, 2026pg_graphql no longer enabled by default
May 30, 2026New behaviour becomes the default for new projects
October 30, 2026Setting applied to all existing projects

The toggle is the "Automatically expose new tables" checkbox on the project creation screen. Unchecked means you're on the new behaviour.

Existing tables are not touched. Supabase's changelog is explicit: they keep their current grants and stay reachable. October 30 changes what happens to tables you create after that date, in projects you already have. If your deploy pipeline creates tables, that's the thing that breaks.

The reason given is that explicit grants are reviewable and greppable, and that anon and authenticated need different privileges anyway. That's true, and it's also the first Supabase default change in a while that makes the insecure configuration harder to reach by accident.

Audit before you grant

Before adding a grant, find out what your project already looks like. Paste this into the SQL editor. It lists every table in public, whether row level security is on, and which Data API roles hold which privileges.

SQL editor
select
  c.relname as table_name,
  c.relrowsecurity as rls_enabled,
  coalesce(
    string_agg(
      distinct g.grantee || ':' || g.privilege_type,
      ', ' order by g.grantee || ':' || g.privilege_type
    ),
    '(none)'
  ) as data_api_grants
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
left join information_schema.role_table_grants g
  on g.table_schema = n.nspname
 and g.table_name = c.relname
 and g.grantee in ('anon', 'authenticated')
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
group by c.relname, c.relrowsecurity
order by c.relrowsecurity, c.relname;

Read the top of the result, not the bottom. The ordering puts rls_enabled = false first on purpose.

A row with rls_enabled = false and anything other than (none) in the grants column is readable, and possibly writable, by anyone who has your anon key. That key is compiled into your JavaScript bundle. It is not a secret.

The row you're looking for looks like profiles | false | anon:SELECT, anon:UPDATE, authenticated:SELECT. That's every profile in your database available to a stranger with curl, and an UPDATE grant on top. Fix those before you spend another minute on the table that isn't showing up.

This is the pattern our scanner flags on Supabase-backed apps constantly. The table that's broken today is rarely the dangerous one. The dangerous one has been working fine for months.

Fix the missing grant

Do these in order. The order matters, because grants take effect immediately and RLS does not exist until you enable it.

1

Turn on row level security and write a policy.

SQL editor
alter table public.orders enable row level security;

create policy "users read their own orders"
  on public.orders for select
  to authenticated
  using (auth.uid() = user_id);

If you enable RLS with no policy, the table returns zero rows to everyone except service_role. That's a safe state, not a broken one, and it's the right thing to have in place before the grant lands.

2

Grant the minimum each role needs.

SQL editor
-- only if this data is genuinely public
grant select on public.orders to anon;

-- the logged-in path
grant select, insert, update, delete on public.orders to authenticated;

-- your backend and edge functions
grant select, insert, update, delete on public.orders to service_role;

Skip the anon line unless you can name who should be reading this table while logged out. Most tables in a real app have no answer to that.

3

Grant sequence usage if you insert.

A serial or identity primary key needs its sequence too, or inserts fail with a second, confusingly similar permission error.

SQL editor
grant usage, select on all sequences in schema public to authenticated;
4

Verify from outside, with no session.

The dashboard runs queries as a privileged role, so it will happily show you data that your app cannot reach and hide data that a stranger can.

Terminal
curl -s "https://YOUR_PROJECT.supabase.co/rest/v1/orders?select=*" \
  -H "apikey: YOUR_ANON_KEY" | head -20

An empty array [] means RLS is doing its job. Rows coming back means anonymous visitors can read this table, and you should be sure you meant that. A 42501 means the grant did not apply to the role you tested.

Re-run that curl after every policy change, not just the first time. A policy written using (true) to "test something" is the single most common way a locked table quietly becomes an open one, and nothing in the dashboard will tell you.

The fix that breaks everything

Search this error and you will land on some version of this:

Do not run this
grant all on all tables in schema public to anon, authenticated;

It works. Your table appears in the API immediately. It also hands anonymous visitors select, insert, update and delete on every table in your database, including the ones you never intended to expose, plus every table you create later if you pair it with alter default privileges.

If RLS is off on any of those tables, and on a vibe-coded project it usually is on at least one, that command is a full database exposure. Supabase's own security docs put it plainly: tables exposed through the Data API without RLS can be accessed by any role with matching grants.

Grants and RLS are separate layers, and people conflate them constantly. A grant decides whether a role can touch the table at all. RLS decides which rows it gets back. When the grant is missing, Postgres rejects the query before RLS is ever consulted, which is why "permission denied" shows up even on a table with perfectly good policies.

The mental model

Grant is the door. RLS is the guest list. Removing the door because someone couldn't get in does not mean the guest list is still being checked.

PGRST205 is a different problem

If your error code is PGRST205, no amount of granting will help. PostgREST builds a cache of the schemas it's told to expose, and your table is not in it. Three causes, in the order they're worth checking:

The schema isn't exposed. Tables in a custom schema like app or billing need that schema added under Project Settings > API > Exposed schemas. public is exposed by default; nothing else is.

The cache is stale. PostgREST reloads on DDL changes, but a table created through an external connection or during a busy migration can miss the signal. Force it:

SQL editor
notify pgrst, 'reload schema';

The name is wrong. Postgres folds unquoted identifiers to lowercase. A table created as "Orders" with quotes is not the same relation as orders, and .from('orders') will never find it. Check the exact relname in the audit query above.

Before October 30

Two things worth doing now rather than the week it lands.

First, make grants part of your migrations. If you use the Supabase CLI, the create-table migration should carry the enable row level security, the policy, and the grants in one file. A table definition that doesn't say who can read it is incomplete, and after October 30 it'll be visibly incomplete instead of silently permissive.

Second, run the audit query and deal with what it shows. Every project that predates this change was built under the old default, which means tables got exposed by creation rather than by decision. The change doesn't clean those up for you. Nothing does.

Why is my Supabase table not showing up in the API?

Since May 30, 2026, new Supabase projects no longer grant the anon, authenticated and service_role roles access to new tables in the public schema automatically. The table exists in Postgres, but the Data API role has no privilege on it, so PostgREST returns 42501 permission denied. You fix it with an explicit GRANT for each role that needs access. If instead you see PGRST205, the table is not visible to PostgREST at all, which is a different problem: an unexposed schema or a stale schema cache.

What is the difference between Supabase error 42501 and PGRST205?

42501 is a Postgres error meaning permission denied for that table. PostgREST found the table and the role was rejected, so the fix is a GRANT. PGRST205 comes from PostgREST itself and means it could not find the table in its schema cache. That points at a table in a schema you have not exposed under API settings, a typo in the table name, or a cache that has not reloaded yet.

When does the Supabase grants change hit my existing project?

October 30, 2026. Supabase applies the setting to all existing projects on that date. Tables you already have keep their current grants and stay reachable. Tables you create after the change need explicit grants, so any migration or dashboard flow that assumed automatic exposure stops working from that point.

Is granting to anon safe?

Only when row level security is on and a policy limits what anon can read. Grants and RLS are separate layers. A grant decides whether a role can touch the table at all, RLS decides which rows it gets back. A table with a select grant to anon and RLS disabled returns every row to anyone holding your anon key, which is public in your JavaScript bundle.

Should I run GRANT ALL ON ALL TABLES IN SCHEMA public TO anon?

No. That single command is the most common answer you will find, and it undoes the entire point of the change. It hands anonymous visitors select, insert, update and delete on every table you have, including the ones you never meant to expose. Grant per table, per role, with the smallest privilege that works.

Find out which of your tables answer to an anonymous request before someone else does. A CheckYourVibe scan checks your live endpoints from the outside, the same way an attacker would.

How-To Guides

Supabase Table Not Showing Up in API (2026): The Grants Fix