PGRST106: The schema must be one of the following

Why the Data API rejects requests for a custom schema with PGRST106 "Invalid schema", and how to reach that data on SupaHost through views or functions in public.

The Data API only serves schemas that are on its exposed list. Ask for anything else and PostgREST refuses with PGRST106. On SupaHost that list is fixed, so the fix is to put an entry point for the data in public.

What you see

Current PostgREST versions (SupaHost runs v14) return HTTP 406 with:

{
  "code": "PGRST106",
  "details": null,
  "hint": "Only the following schemas are exposed: public, storage, graphql_public, pgmq_public",
  "message": "Invalid schema: billing"
}

Older PostgREST versions worded the same error as "The schema must be one of the following: ...", which is why many search results use that phrase.

You trigger it by selecting a schema in the client:

const { data, error } = await supabase.schema('billing').from('invoices').select('*')
// or: createClient(url, key, { db: { schema: 'billing' } })

Why it happens

PostgREST reads Accept-Profile / Content-Profile headers (set by .schema() or the db.schema option) and only accepts schemas listed in its configuration. Keeping internal schemas off that list is a safety feature: nothing in them is reachable over HTTP, whatever the grants say.

How to fix it

On SupaHost the exposed schemas are public, storage, graphql_public and pgmq_public, and they can't be changed. Pick one of these approaches.

  1. Move the tables to public if they're meant to be app data anyway:

    alter table billing.invoices set schema public;
    

    Then enable RLS and add policies as you would for any table in public.

  2. Keep the tables private and expose a view in public. Use security_invoker so the caller's RLS policies and grants apply, not the view owner's:

    create view public.my_invoices
    with (security_invoker = true) as
      select id, customer_id, total_cents, due_on
      from billing.invoices;
    
    grant select on public.my_invoices to authenticated;
    grant usage on schema billing to authenticated;
    grant select on billing.invoices to authenticated;
    

    RLS on billing.invoices still decides which rows each user sees.

  3. Expose a function in public when the client needs to do something rather than read a table:

    create or replace function public.mark_invoice_paid(invoice_id bigint)
    returns void
    language sql
    security invoker
    set search_path = ''
    as $$
      update billing.invoices set paid_at = now() where id = invoice_id;
    $$;
    

    Call it with supabase.rpc('mark_invoice_paid', { invoice_id: 42 }). If the function must bypass RLS, read security definer functions first.

  4. Remove the .schema('billing') call or db.schema option from your client so requests go to public again.

On SupaHost

There's no setting in the dashboard or Studio to add a schema, so don't spend time looking for one. Code that only reads from a server can also skip the Data API: Edge Functions can call a public function, and Business plans can connect to Postgres directly through the SSH tunnel.

pgmq_public is exposed so queue functions can be called over HTTP, but by default only service_role may execute them. A permission error there is a grants issue, not PGRST106.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us