23 Schemas and search_path
A schema in PostgreSQL is a namespace inside a database — a way to group tables, views, functions, sequences, and types under a name. Every database has at least one (public), and most production deployments use several. Schemas are how you organize a large project, host multiple tenants in one database, and isolate extensions from application code. Once you understand search_path, you understand how PostgreSQL resolves unqualified names.
What you’ll learn
- Creating and dropping schemas
- The
schema.tablequalified-name syntax - The
search_pathand how name resolution works - Per-role and per-database
search_pathdefaults - Common patterns: an
appschema, multi-tenant schemas, separating extensions
Example
When you write SELECT * FROM events, PostgreSQL looks up the name in each schema listed in search_path, in order, and uses the first match. The default search_path is "$user", public — meaning “first look in a schema named after the connecting user, then public.” If a schema with the user’s name exists, it wins.
You can change the search_path per session (SET search_path TO ...), per role (ALTER ROLE alice SET search_path TO ...), or per database (ALTER DATABASE app_db SET search_path TO ...). The last two persist across reconnections.
Schema separation is the standard way to keep extension objects (postgis, pg_trgm) out of public, to give each tenant its own namespace, or to logically group audit.*, app.*, staging.* tables.
Run the example
To run:
Common pitfalls
- Forgetting
search_pathin a function: aSECURITY DEFINERfunction uses its owner’s privileges but, without a function-level setting, the caller’ssearch_path. Set a path containing only trusted schemas, withpg_templast (for example,SET search_path = pg_catalog, app, pg_tempwhenappis trusted), and qualify sensitive object names. - Putting extensions in
public: extensions install their objects in whatever schema is current. Many teamsCREATE SCHEMA extensions; ALTER ROLE ... SET search_path = "$user", public, extensions;and create extensions there. - Dropping a schema with dependencies fails. Use
DROP SCHEMA name CASCADEto drop the schema and its dependent objects. It can be rolled back inside a transaction; after commit, recovery requires a backup or recreation. - Forgetting that
\dtonly shows tables in the currentsearch_pathschemas. Use\dt *.*to see everything.
For multi-tenant systems, schema-per-tenant works at small scale (tens to hundreds of tenants) but becomes painful as the catalog grows. Row-level isolation (a tenant_id column with ROW SECURITY policies) is the more common large-scale pattern.
Run SHOW search_path; then SET search_path TO app, public; and \dt in interactive psql. Create a table in two schemas with the same name and see which one wins. Then drop the schema: DROP SCHEMA app CASCADE; (use only a disposable demo schema).
Downloads
SQL file: schemas.sql · All lesson files · Setup guide