PostgreSQL · 42501

permission denied for schema public — usage grants in Supabase

Luca Urti

permission denied for schema public

What does “permission denied for schema public” mean in Supabase?

The role making the request lacks usage on the schema, which is the privilege that allows a role to reference anything inside it at all. It sits one level above table grants: without usage on the schema, a grant on a table within it is unreachable, and PostgreSQL stops at the schema. In Supabase this appears on projects where the default grants were altered, on schemas created by hand or by a generated migration, and after a revoke run to tighten things up that removed more than intended.

Usage is not access

Granting usage does not expose data. It only makes objects inside the schema addressable; each table still needs its own grant, and each row still needs to pass RLS. Reading this error as “the schema is locked down, good” and leaving it in place is a mistake for the same reason a missing table grant is: the moment somebody grants what the app needs, whatever has no RLS becomes reachable.

The reverse also holds: a schema you deliberately keep out of the API — an internal one holding audit rows or billing state — should not be in PostgREST's exposed schema list at all. That is a stronger boundary than grants, because it is not undone by a later grant all.

The fix

grant usage narrowly

grant usage on schema public to anon, authenticated;

-- Then the specific tables, not the schema:
grant select on public.posts to anon;
grant select, insert, update, delete on public.posts to authenticated;

-- Default privileges apply to tables created LATER by this role. Convenient,
-- and the reason a table added next month is already granted before anyone
-- has written a policy for it. Set it knowingly or not at all.
--   alter default privileges in schema public
--     grant select on tables to anon;

The fix that works and costs you the database

grant all privileges on schema public to anon; fixes it and gives the anonymous role the right to create objects in your schema, not merely to read them. Combined with alter default privileges, it also pre-grants every table you have not created yet — so a table added in a future migration is exposed from its first second, before anybody has written a policy for it. That is the failure mode where a scan of the deployed app finds nothing and the repository shows the leak waiting in a migration.

Whether the trap is already in your repo is a question you can answer

Sentris reads the SQL and the client code, so it reports the shortcut above where it was actually taken — a service_role key in a browser bundle, a policy that is using (true), a table with RLS switched off. A scan needs no account and no card. How often it is wrong is measured and published.

Scan my app