List All Schemas in Your PostgreSQL Database
How to List All Schemas in a PostgreSQL Database
information_schema.schemata is the SQL-standard view for listing schemas, and it returns one row per schema in the current database that the connected role owns or holds a privilege on. Behind it sits pg_namespace, the system catalog that stores every schema with its owner and access list. The two answer slightly different questions, and picking the wrong one is how a schema audit misses the schemas the connected role cannot access.
Purpose and Overview
A schema is a namespace. Each one holds its own tables, views, functions, types and sequences, and two schemas can contain objects with the same name without conflict. A fresh database has the public schema plus the system schemas. A production database often has dozens more: one per application module, one per tenant, one per migration tool, and a few left behind by projects nobody remembers.
A schema list is the first step of several routine jobs: checking who owns what before a role is dropped, finding empty or abandoned schemas, confirming that a tenant schema was created, and scoping a pg_dump -n run. The queries below cover the quick list, a detailed audit that adds owner, table count, comment and access, and a filtered list that leaves the system schemas out.
Sample Code
Simple list using information_schema:
1SELECT schema_name, schema_owner
2FROM information_schema.schemata
3ORDER BY schema_name;
Detailed audit using pg_namespace:
1SELECT
2 n.nspname AS schema_name,
3 pg_catalog.pg_get_userbyid(n.nspowner) AS owner,
4 count(c.oid) FILTER (WHERE c.relkind IN ('r', 'p'))
5 AS tables,
6 has_schema_privilege(n.nspname, 'USAGE') AS can_use,
7 has_schema_privilege(n.nspname, 'CREATE') AS can_create,
8 obj_description(n.oid, 'pg_namespace') AS comment
9FROM pg_catalog.pg_namespace n
10LEFT JOIN pg_catalog.pg_class c ON c.relnamespace = n.oid
11WHERE n.nspname !~ '^pg_'
12 AND n.nspname <> 'information_schema'
13GROUP BY n.oid, n.nspname, n.nspowner
14ORDER BY n.nspname;
List only user-created schemas from the standard view:
1SELECT schema_name
2FROM information_schema.schemata
3WHERE schema_name !~ '^pg_'
4 AND schema_name <> 'information_schema'
5ORDER BY schema_name;
Tables inside one schema:
1SELECT tablename
2FROM pg_tables
3WHERE schemaname = 'myapp'
4ORDER BY tablename;
Notes: The second query returns every schema in pg_namespace that is not a system schema, whether or not the current role has any privilege on it. Replace myapp in the last query with a real schema name. In psql, \dn gives a quick version of the first query, \dn+ adds privileges and descriptions, and \dnS includes the system schemas.
Code Breakdown
The first two queries read different sources and return different row sets, so the breakdown follows the data rather than the statements.
information_schema.schemata
The view reports catalog_name (always the current database), schema_name, and schema_owner. It also has default_character_set_catalog, default_character_set_schema, default_character_set_name and sql_path, all of which apply to a feature not available in PostgreSQL, so they carry no useful data. Because the view filters by privilege, a role that has no rights on a schema does not see it here.
pg_namespace and pg_get_userbyid
pg_namespace has four columns: oid, nspname, nspowner and nspacl. The owner is stored as an OID that references pg_authid, so pg_get_userbyid(n.nspowner) converts it to a role name. The catalog is not filtered by privilege, which makes it the right source for an audit.
Counting tables with pg_class
pg_class stores relations, and relnamespace ties each one to a row in pg_namespace. The LEFT JOIN keeps schemas that have no relations at all, so an empty schema shows a table count of zero instead of disappearing. The FILTER clause counts only relkind values r (ordinary table) and p (partitioned table); indexes, sequences and TOAST tables share the same catalog and would otherwise inflate the number.
Privilege checks
has_schema_privilege(schema, privilege) takes USAGE or CREATE and answers for the current role. When can_use is false, the schema exists but the role cannot look up objects in it. The two-argument form checks the current user; a three-argument form accepts a role name or OID first.
The system-schema filter
Names that begin with pg_ are reserved for system purposes and cannot be created by users, so the regular expression '^pg_' removes pg_catalog, the per-session temporary schemas and any other system namespace. information_schema does not begin with pg_, so it is excluded by name. A LIKE 'pg_%' test is a common mistake here: in a LIKE pattern the underscore matches any single character, so it would also hide a user schema called pgstats or pgx. The regular expression or an escaped LIKE 'pg\_%' avoids that.
Key Schema Concepts
A few facts about how schemas behave explain what the queries return.
The public schema
Every new database contains a schema named public. Objects created without a schema name land in the first valid schema on the search path, which by default is public for most roles.
pg_catalog and temporary schemas
Each database contains pg_catalog, which holds the system tables and all built-in data types, functions and operators. The current session's temporary-table schema is pg_temp_nnn, and it is always searched for relation and data type names if it exists. Because both start with pg_, the user-schema filters above skip them.
search_path
search_path is the comma-separated list that decides which schema supplies an unqualified name. The default is "$user", public: PostgreSQL first looks for a schema with the same name as the current role, then public. Entries that name a nonexistent schema, or one the role has no USAGE on, are silently ignored. That silent behavior is a common cause of "relation does not exist" errors after a schema is renamed.
1SHOW search_path;
2SET search_path TO myapp, public;
USAGE and CREATE
USAGE on a schema allows a role to look up objects inside it, provided the objects' own privileges are also met. CREATE allows creating objects in the schema. Without USAGE, the object names can still be seen by querying the system catalogs, which is why pg_namespace shows schemas that information_schema.schemata hides.
Practical Applications
Schema listings feed a handful of recurring tasks.
Tenant and module inventory
On a database that uses one schema per tenant, the detailed query shows each schema, its owning role and its table count. A tenant schema with zero tables is usually a failed provisioning run. Schemas whose table counts differ from the rest are the ones to compare against the template before a migration.
Pre-drop and ownership review
Before dropping a role, list the schemas it owns. A schema can only be dropped by its owner or a superuser, and the owner can drop it even when other roles own objects inside. The owner column shows which schemas need ALTER SCHEMA ... OWNER TO first.
Scoping a backup
pg_dump -n selects one schema and everything it contains, and -N excludes schemas by pattern. Listing the user schemas first gives the exact names to pass. A schema-level dump does not pull in other objects the schema depends on, so it cannot be assumed to restore alone into an empty database.
Access review
The can_use and can_create columns show what the connected role can do. Run the query as an application role to confirm it reaches the schemas it is supposed to, and as a read-only role to confirm it cannot create objects anywhere unexpected.
Version Compatibility
Every view, catalog column and function in the queries above appears in the current manual under the names used here, so no version-specific rewrite is needed for them.
The notable version change concerns the public schema. From PostgreSQL 15 the default configuration supports the pattern of user-private schemas, in which ordinary users cannot create objects in public. On earlier releases, and on databases upgraded from them, that protection has to be added with REVOKE CREATE ON SCHEMA public FROM PUBLIC. The can_create column in the detailed query shows the current state for any role you test.
Best Practices
- Audit from pg_namespace, report from information_schema — the catalog shows every schema, while the view shows only what the role can access, so use the one that matches the question.
- Never filter system schemas with an unescaped LIKE —
LIKE 'pg_%'also matches user schemas such aspgstats; use~ '^pg_'orLIKE 'pg\_%'. - Comment every schema —
COMMENT ON SCHEMA myapp IS 'Billing tables'makes the description column useful;obj_description()returns null when no comment exists. - Create schemas with an owner —
CREATE SCHEMA myapp AUTHORIZATION app_ownersets ownership in one step. - Treat DROP SCHEMA ... CASCADE as irreversible — it removes every contained object and may also remove objects in other schemas that depend on them; list the contents first.
- Check the search_path of application roles — a role whose path names a schema it cannot use silently skips it, so verify with
has_schema_privilege().
References
- PostgreSQL: Schemas — Schema creation and dropping, the public schema, pg_catalog, and the reserved pg_ prefix.
- PostgreSQL: information_schema.schemata — Columns of the schemata view and its access rule.
- PostgreSQL: pg_namespace catalog — The oid, nspname, nspowner and nspacl columns.
- PostgreSQL: Client Connection Defaults — search_path behavior, the default value, and the temporary schema.
- PostgreSQL: Privileges — USAGE and CREATE on schemas.
- PostgreSQL: Pattern Matching — LIKE wildcards, the escape character, and POSIX regular expressions.
- PostgreSQL: System Information Functions — has_schema_privilege() and obj_description().
- andyatkinson/pg_scripts: list_schemata.sql — original seed script.
Posts in this series
- How Many Connections Can Your PostgreSQL Database Handle?
- PostgreSQL Backend Connections via pg_stat_database
- pg_blocking_pids — Find Blocking Queries in PostgreSQL
- List PostgreSQL Databases by Size with Access Check
- Assess PostgreSQL Database Sizes Quickly and Easily
- Unveiling Your PostgreSQL Server - A Diagnostic Powerhouse
- Keep Your PostgreSQL Database Clean, Identify Idle Connections
- Query the PostgreSQL Configuration
- pg_is_in_recovery — Monitor PostgreSQL Standby Status
- ALTER SEQUENCE RESTART WITH in PostgreSQL — Examples
- Monitor Running Queries in PostgreSQL using pg_stat_activity
- Monitor PostgreSQL Active Sessions with pg_stat_activity
- PostgreSQL Error Handling Settings via pg_settings
- PostgreSQL File Location Settings Query via pg_settings
- Inspect PostgreSQL Lock Management Settings with pg_settings
- PostgreSQL Logging Configuration Query via pg_settings
- Monitor PostgreSQL Memory Settings with pg_settings
- PostgreSQL Table Row Count Estimates with SQL
- List PostgreSQL Tables by Size with SQL
- PostgreSQL WAL Settings Query Guide
- log_parser_stats, log_planner_stats, log_executor_stats — PostgreSQL
- PostgreSQL SSL Settings Query Guide
- PostgreSQL Resource Settings Query Guide
- PostgreSQL Replication Settings Query Guide
- PostgreSQL Query Planning Settings Query Guide
- PostgreSQL Preset Options Settings Query Guide
- PostgreSQL Miscellaneous Settings Query Guide
- Count PostgreSQL Sessions by State with SQL
- Kill Idle PostgreSQL Sessions with SQL
- GRANT SELECT on All Tables in PostgreSQL — with Examples
- pg_stat_user_tables — Find Insert-Only Tables in PostgreSQL
- Detect Soft Delete Patterns in PostgreSQL
- List PostgreSQL Object Comments with SQL
- List Foreign Key Constraints in PostgreSQL
- List PostgreSQL Enum Types and Their Values with SQL
- List All Views in a PostgreSQL Database with SQL
- Find PostgreSQL Tables Without a Primary Key
- List PostgreSQL Partitioned Tables with SQL
- List All Schemas in Your PostgreSQL Database
- pg_stat_database — Query PostgreSQL Database Statistics
- List PostgreSQL Roles and Their Privileges
- Scrubbing Email PII in PostgreSQL for GDPR Compliance
- List Installed Extensions in PostgreSQL
- List PostgreSQL Collations with pg_collation and ICU
- PostgreSQL Replica Identity for Logical Replication
- Monitor PostgreSQL Vacuum Progress with pg_stat_progress_vacuum
- Monitor PostgreSQL Wait Events Using pg_stat_activity
- Monitor PostgreSQL Replication Lag with pg_stat_replication
- List PostgreSQL Wait Events with the pg_wait_events View
- PostgreSQL Column-Level Permissions Audit Query
- List All PostgreSQL Triggers with Their State
- timestamptz and tzdata: Avoid Shifted PostgreSQL Timestamps
- Inspect PostgreSQL Sequences with the pg_sequences View
- List PostgreSQL Functions with pg_proc
- Query PostgreSQL Tablespace Info with pg_tablespace
- Audit PostgreSQL Authentication with pg_hba_file_rules
- List PostgreSQL Materialized Views with pg_matviews
- Audit Row-Level Security Policies with pg_policies