_realtime, supabase_functions, etc.) you’ll occasionally see in pg_namespace. This page explains what each schema contains, who owns it, and which ones are safe to touch.
For the AI-schema-specific queryability story, see Querying the ai schema. For RLS, see RLS Model. For migration boundaries, see Migrations.
The five user-visible schemas
| Schema | Owner | Purpose | Safe to migrate? |
|---|---|---|---|
public | You | Your application tables | Yes |
extensions | You | User-installed Postgres extensions | Yes |
ai | Platform | Sources, KBs, agents, runs, sessions, workflows | No |
auth | GoTrue | User accounts, sessions, OAuth identities | No |
storage | Storage API | Bucket and object metadata | No |
public — your tables
Empty by default. You add your application tables here: users (or profiles joining to auth.users), posts, orders, etc. RLS is off for new tables in public unless you explicitly enable it (ALTER TABLE my_table ENABLE ROW LEVEL SECURITY).
PostgREST exposes public at /rest/v1/* without needing the Accept-Profile header — it’s the default schema for the API.
ai — the AI surface’s state
Where the typed /api/* endpoints store their data. 35+ tables for sources, knowledge_bases, indexed_sources, chunks, embeddings, agents, agent_sessions, agent_runs, workflows, workflow_executions, etc. Full inventory in Querying the ai schema.
Don’t migrate ai.*. The platform’s services assume the schema’s invariants — modifying them risks data corruption or service-side breakage. Read freely (RLS lets authenticated SELECT most tables); never schema-change.
PostgREST exposes ai at /rest/v1/* with Accept-Profile: ai for reads and Content-Profile: ai for writes. RLS gates which rows each role can touch.
auth — GoTrue’s state
User accounts (auth.users), refresh tokens (auth.refresh_tokens), OAuth identities (auth.identities), MFA factors (auth.mfa_factors), email-change records, recovery tokens. Managed entirely by GoTrue’s own migrations; the project init SQL just creates the schema.
Don’t migrate auth.*. GoTrue runs its own schema migrations on startup; conflicting changes break the auth surface.
PostgREST does not expose auth — there’s no Accept-Profile that gets you in. Use the typed /auth/v1/* API instead.
storage — Storage API’s state
storage.buckets (one row per bucket), storage.objects (one row per uploaded file), plus a handful of helper tables and functions (storage.foldername(), storage.filename()). Managed by the Storage API.
PostgREST exposes storage at /rest/v1/* (it’s in the schemas list). You can read storage.objects to list files via SQL, and RLS policies on storage.objects are how you gate object access in private buckets. Don’t write to storage.objects directly — go through /storage/v1/object/..., which keeps the underlying S3 backend in sync.
See Storage policies for the policy patterns.
extensions — Postgres extensions land here
When you CREATE EXTENSION foo, the extension’s objects live in whatever schema the extension specifies, which by Powabase convention is extensions:
extensions. The platform pre-installs pg_net and vector here at project provision time.
Internal schemas you’ll see
These exist for platform internals and aren’t intended for user code, but you’ll notice them inpg_namespace listings:
| Schema | Purpose |
|---|---|
_realtime | Realtime’s per-tenant configuration and replication state |
supabase_functions | The http_request() trigger function plus the audit table for triggered hooks (used by DB webhooks) |
realtime | The realtime.send() and realtime.broadcast_changes() SQL functions |
graphql_public | pg_graphql’s mount point (queryable via POST /rest/v1/rpc/graphql) |
vault | Vault extension’s encrypted secrets storage (preloaded but no platform code uses it) |
pgsodium, pg_catalog, information_schema, etc. | Standard Postgres internals |
realtime and supabase_functions from your own code — that’s why they’re documented above. The rest are internal.
search_path and what schemas resolve
Your Postgres session’ssearch_path controls which schemas are searched for unqualified table references. The default is "$user", public, extensions — meaning SELECT * FROM users looks in public first, then extensions.
To query ai.* from psql without qualifying every table:
Accept-Profile / Content-Profile headers — you only think about it for direct SQL connections.
What schema your role can see
Each Postgres role has differentUSAGE grants on each schema. The defaults that matter:
| Role | public | ai | auth | storage |
|---|---|---|---|---|
anon | USAGE | USAGE (RLS denies all) | — | USAGE |
authenticated | USAGE | USAGE (RLS-gated) | — | USAGE |
service_role | USAGE | USAGE (RLS bypassed) | — | USAGE |
supabase_admin (you in psql / migrations) | USAGE | USAGE | USAGE | USAGE |
auth.* schema isn’t granted to anon / authenticated / service_role — they can’t even see it in \dn output, let alone query auth.users.
Next steps
Querying the ai schema
What’s in ai.* and how to query it under RLS.
Extensions
Which extensions are preloaded vs CREATE EXTENSION-able.
RLS Model
The role mapping that drives which schemas / rows each request can see.
Migrations
The schema boundary you should not cross when migrating.