# 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.

Source: https://supahost.app/kb/pgrst106-schema-not-exposed · Updated 2026-11-11

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:

```json
{
  "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:

```js
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:

   ```sql
   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:

   ```sql
   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:

   ```sql
   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](/kb/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](/docs/platform/ssh).

`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.

## Related

- [Exposed schemas](/docs/api/exposed-schemas)
- [Calling database functions (RPC)](/docs/api/rpc)
- [PostgREST not seeing new tables (schema cache)](/kb/postgrest-schema-cache)
- [Queues with pgmq](/docs/cron-queues/queues)
