← All posts
·8 min read·GuardLayer

Supabase: Could not find the table in the schema cache

SupabasePostgRESTAuthData APISecurity

Could not find the table 'public.X' in the schema cache (PGRST205, HTTP 404) means PostgREST has no table or view by that name in the schema it searched: it doesn't exist in this project, it lives in another schema, the name doesn't match exactly, or the cache is stale. If the missing table is public.users, you were reaching for auth.users: create a public.profiles table instead of exposing the auth table.

The error comes from PostgREST, the server behind supabase.from(). It isn't telling you about permissions or RLS. It's saying the name you asked for isn't in its map of the database, so it never ran a query at all.

What does "Could not find the table in the schema cache" mean?

It means PostgREST looked up the table name in its cached copy of your schema and found nothing. The message is built in PostgREST's Error.hs as "Could not find the table '" <> schemaName <> "." <> relName <> "' in the schema cache", with code PGRST205 and status 404. Supabase's error code reference describes PGRST205 as "Caused when the table specified in the URI is not found."

So this call:

const { data, error } = await supabase.from("users").select("id, email");
// error.code    === "PGRST205"
// error.message === "Could not find the table 'public.users' in the schema cache"

asked for users in the default schema, which is public unless you changed it, and PostgREST had nothing by that name.

When a table in the same schema has a close enough name (PostgREST uses a fuzzy match with a 0.75 minimum score), the error also carries a suggestion such as Perhaps you meant the table 'public.items'. Read it before anything else. It catches most typos and singular/plural mix-ups.

One split worth knowing: reading PostgREST's source, the table list it caches isn't filtered by privileges. A table that exists but isn't granted to your role comes back as 42501 permission denied for table, not as PGRST205. If you just created a table after Supabase's October 30 Data API grant change and get a 404 rather than a 42501, look at the name and schema first.

The five causes, in the order to check them

1. You're talking to a different database. The table exists in your local stack or in another project, and your app points somewhere else. Log process.env.NEXT_PUBLIC_SUPABASE_URL in the failing environment and compare the project ref with the one you're looking at in the dashboard. A migration you ran with supabase migration up locally isn't on the hosted project until you supabase db push it. Preview branches have their own databases too.

2. The table is in another schema. PostgREST resolves an unqualified name against one schema only. Supabase's KB page on this error explains that when several schemas are exposed, "the first entry in the list is treated as the default". If your table is api.orders, either select the schema per query or set it on the client:

// per query
await supabase.schema("api").from("orders").select("*");

// or for the whole client
import { createBrowserClient } from "@supabase/ssr";
export const supabase = createBrowserClient(url, publishableKey, {
  db: { schema: "api" },
});

The schema also has to be in the Exposed schemas list in your project's Data API settings. A schema that isn't exposed fails with a different error, PGRST106.

3. The name doesn't match exactly. If the table was created with quotes, as "Profiles", Postgres stored it with a capital P and .from("profiles") won't find it. Tools that generate SQL from a GUI or an ORM model do this often. Rename it to lowercase rather than quoting it everywhere:

alter table public."Profiles" rename to profiles;

4. The schema cache is stale. PostgREST loads the schema at startup and on reload signals, so a table created a moment ago can be missing until the next reload. Schema changes normally show up on their own. When one doesn't, run this in the SQL Editor (it's the command in Supabase's KB and in the PostgREST schema cache docs):

notify pgrst, 'reload schema';

This is the whole answer to "supabase schema cache reload / refresh / clear". There is no separate clear button. If a reload doesn't help, the cache wasn't the problem; go back to causes 1 to 3.

5. The table is public.users and it was never yours. That one gets its own section.

Why "Could not find the table 'public.users'" is different

Supabase keeps users in auth.users, and its user data guide says plainly: "For security, the Auth schema is not exposed in the auto-generated API." So .from("users") looks in public, finds nothing and returns PGRST205. Nothing is broken. That table was never meant to be reachable from the browser.

The fixes people reach for next are where the real damage happens. Each one makes the error go away by publishing every user's email.

The most common is a view:

-- "Could not find the table 'public.users' in the schema cache"
-- quick fix: give the API a users table to find
create view public.users as
  select id, email, raw_user_meta_data
  from auth.users;

grant select on public.users to anon, authenticated;
guardlayer scan · supabase/migrations/20261109090000_users_view.sqlLive engine output
Passed with warnings
92/100 · A
  • Warningsupabase/migrations/20261109090000_users_view.sql:7

    Table or view granted to the anon role without RLS

    For a table, enable RLS (ALTER TABLE <t> ENABLE ROW LEVEL SECURITY;) and add scoped policies. For a view, create it WITH (security_invoker = true) and repeat that clause on every CREATE OR REPLACE VIEW. Materialized views and foreign tables cannot be protected: revoke the anon grant or move them to a schema the Data API does not expose. Prefer per-table grants over ON ALL TABLES IN SCHEMA public. Only keep an anon grant for genuinely public data that is still RLS-protected.

The view runs with its owner's privileges, so it reads auth.users as postgres for whoever calls it. With that grant, GET /rest/v1/users?select=email and your publishable key return the whole list to anyone. Supabase's Security Advisor reports this as lint 0002, Exposed Auth Users.

The other two:

  • Adding auth to Exposed schemas. That list is how a schema goes on the public API. The auth schema stays off it on purpose; don't put it there.
  • Switching to the service-role key in client code. It makes auth.admin.listUsers() work from the browser by shipping the key that bypasses RLS on every table. If you've done this, read what to do about an exposed service role key and rotate it.

How do I fix "Could not find the table 'public.users'"?

Create your own public.profiles table that holds only the columns other users may see, keyed to auth.users(id), with RLS on:

create table public.profiles (
  id uuid primary key references auth.users (id) on delete cascade,
  display_name text,
  avatar_url text
);

alter table public.profiles enable row level security;

revoke all on public.profiles from anon, authenticated;
grant select on public.profiles to authenticated;

create policy "signed-in users read profiles" on public.profiles
  for select to authenticated using (true);

Then query profiles instead of users:

const { data } = await supabase.from("profiles").select("id, display_name, avatar_url");

Leave email and phone out of profiles. For the current user's own email, supabase.auth.getUser() already returns it. For an admin screen that lists accounts, call auth.admin.listUsers() from a route handler with the service-role key and an admin check in front. The full pattern, including the signup trigger and joins, is in how to query the auth.users table without leaking emails.

The same goes for public.profiles in the schema cache: that error almost always means the tutorial's migration never ran on this project (cause 1), not that something deeper is wrong.

The sibling errors: function, column, relationship

The same file produces three related messages, and the same triage applies:

  • Could not find the function ... in the schema cache (PGRST202, 404). The RPC name, schema or argument names don't match a function PostgREST knows. Argument names count: rpc("get_stats", { userId }) won't match a parameter called user_id.
  • Could not find the 'avatar_url' column of 'profiles' in the schema cache (PGRST204, 400). Usually a column added after the last reload, or a typo in an insert or update payload.
  • Could not find a relationship between 'posts' and 'profiles' in the schema cache (PGRST200, 400). Embedding needs a foreign key between the two tables. Add it, then reload.

Quick self-check

Run this in the SQL Editor to see where the name actually lives:

select n.nspname as schema, c.relname as name, c.relkind
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relname ilike '%users%'
  and c.relkind in ('r', 'v', 'm', 'p', 'f');

No row means the table isn't in this database (cause 1). A row in another schema is cause 2. Mixed case in name is cause 3. And a public.users view over auth.users is a leak: drop it.

grep -rniE "create (or replace )?view +public\.users" supabase/migrations/
grep -rn "NEXT_PUBLIC_.*SERVICE_ROLE" .

To see your project the way the API sees it, GuardLayer's free Live check connects read-only and checks which tables anyone with your publishable key can read, without touching any rows.

FAQ

What is PGRST205 in Supabase? It's PostgREST's "table not found" error, returned with HTTP 404 when the table in the request path isn't in the schema cache for the schema being searched.

How do I reload the Supabase schema cache? Run notify pgrst, 'reload schema'; in the SQL Editor. There is no separate "clear cache" action; a reload rebuilds it.

Why do I get "Could not find the table 'public.users'"? Because Supabase stores users in auth.users, which isn't exposed to the API. Create a public.profiles table instead of a view over auth.users.

Is PGRST205 a permissions problem? No. A table that exists but lacks a grant returns 42501 permission denied. PGRST205 means the name wasn't found at all.

Can I expose the auth schema to fix it? Don't. It puts the auth tables behind the public API. Copy the fields you need into public.profiles and keep admin lookups on the server.

Check your Supabase Auth settings and tables, free

Connect your Supabase project read-only. GuardLayer checks your Auth settings (leaked passwords, email confirmation, secure email change, token rotation, CAPTCHA, MFA) and your RLS, policies and grants, and says how to fix each problem. It never reads your rows and can't change anything.

Check my Supabase project

Prefer to check code instead? Scan your repo, no signup.

Keep reading