9.3 CREATE SCHEMA and the search_path Setting
Right, let’s talk about schemas. If a database is a big filing cabinet, a schema is one of the drawers in that cabinet. It’s a namespace, a neat little box where you can group your tables, views, functions, and other objects. This is fantastically useful for organization, multi-tenant setups, or just keeping your experimental mess away from your production data.
The command to create one is about as straightforward as it gets:
CREATE SCHEMA my_awesome_app;
Boom. You now have a drawer labeled my_awesome_app. You can start putting tables in it by using the dot notation:
CREATE TABLE my_awesome_app.users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL
);
Now, you might be thinking, “Great, I’ll just create everything in the default public schema and call it a day.” And you could. For a tiny, trivial app, that’s fine. But the moment you need to juggle extensions, third-party data, or separate logical components, you’ll be glad you used schemas. It’s like having a toolbox where everything is just thrown in versus one with little compartments for screws, nails, and wrenches.
The search_path: Your Database’s To-Do List
Here’s where things get interesting, and where most people get tripped up. PostgreSQL doesn’t automatically look inside every drawer when you ask for a hammer (users table). It has a to-do list called search_path. This is a list of schemas it will check, in order, to find an object you’ve referenced without a schema name.
Let’s see what yours is set to right now:
SHOW search_path;
You’ll probably get "$user", public. This is the default. Let’s break down this bit of slightly absurd genius:
"$user": PostgreSQL first looks for a schema with the same name as your current user. If it doesn’t exist, it moves on. It’s a weird, mostly vestigial feature from way back. I’ve seen it used seriously maybe twice in my career.public: This is the fallback. If the object isn’t in a"$user"schema, it looks inpublic.
This is why you can run CREATE TABLE users (...); and it happily goes into the public schema without you even thinking about it. The search_path told it to look there.
Why You Absolutely Must Tame the search_path
The search_path is a massive foot-gun, and I’ve seen it blow off toes more times than I can count. The biggest pitfall? Creating things in the wrong schema without realizing it.
Imagine this: your search_path is "$user", public. You intend to create a table in the public schema. But unbeknownst to you, a schema named bob exists (because your username is bob). You run CREATE TABLE important_data (...);. Where does it go? It goes into the bob schema, because that’s the first schema in the search_path that exists. You then try to query it from another user whose search_path doesn’t include bob, and… it’s gone. Cue confusion and panic.
The solution is deliberateness. Always explicitly specify the schema when creating critical objects.
-- Good. Explicit. Boring. Safe.
CREATE TABLE public.important_data (...);
-- Dangerously ambiguous. Did it go where you *think* it went?
CREATE TABLE important_data (...);
Setting the search_path Properly
For your applications, you should almost always set the search_path explicitly at the session or role level. This is a best practice that prevents a whole class of nasty bugs.
You can set it for your current session:
-- Look in 'my_awesome_app' first, then 'public' if not found.
SET search_path TO my_awesome_app, public;
But the real pro move is to set it for a specific database user (role) so it happens automatically every time they connect. This is what you want for your application’s database user.
ALTER ROLE my_app_user SET search_path TO my_awesome_app, public;
Now, when my_app_user connects, any unqualified query like SELECT * FROM users; will look in my_awesome_app first. This keeps your code clean and schema-aware without littering it with explicit schema names everywhere. You can reference tables directly, and PostgreSQL knows exactly which drawer to look in.
The Public Schema is Not Your Buddy
I need to be direct with you: the public schema is a trap. It’s a default, and everyone has write access to it by default. This makes it a dumping ground and a security risk. For any serious application, your first step after creating a database should be to REVOKE the default CREATE permission from the public role on the public schema.
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
Yes, the capitalization is maddening. PUBLIC is a special role that represents every user. public is the schema. It’s a terrible naming choice, and we all have to live with it. This command essentially says, “stop letting every random user create tables in the public schema.” It’s basic database hygiene. Create your own schema, grant privileges specifically on that to your app user, and keep the public schema clean for, well, public extensions or other truly shared objects.