PostgreSQL error 42P01
relation does not exist
PostgreSQL found no table, view or sequence with that exact name in the schemas it searched. Usually the name was created with capitals in double quotes, the table is in a schema that isn’t on your search_path, or you’re connected to a different database.
ERROR: relation "customers" does not exist
Tested on PostgreSQL 18.6 (also 14–17) · Updated 9 October 2026
What it means
“Relation” is PostgreSQL’s word for anything table-like: tables, views, materialised views, sequences, indexes and foreign tables. The server looked up the name in your statement and found nothing with that exact spelling in any schema it searched.
The lookup happens before any row is read, so nothing ran. The caret under LINE 1 points at the name
it couldn’t find. DROP TABLE says table "x" does not exist instead; it’s the same error code and the
same causes.
Two related messages share the code. missing FROM-clause entry for table "c" means the query uses
c.something but no table or alias c is in its FROM clause: add the table, or fix the alias.
invalid reference to FROM-clause entry for table "customers" means the table has an alias, so it must
be called by that alias (c.email, not customers.email); the hint names it.
Common causes
- Capitals and double quotes. PostgreSQL folds unquoted names to lower case. A table created as
"Customers"(common with ORMs, GUI tools and tables migrated from MySQL or SQL Server) can only be reached as"Customers";Customersandcustomersboth becomecustomers. - The table is in a schema that isn’t on your
search_path, such asbilling.invoiceswhen the path is the default"$user", public. - You’re connected to a different database, server or port. Each database has its own tables, and
a connection can’t see another database’s tables. Connecting to the default
postgresdatabase by mistake is the classic case. - A typo or an old name: singular for plural, the name before a rename, or a sequence that kept
its original name (
bills_id_seqafterbillswas renamed toinvoices). - You have no
USAGEon the schema. Schemas you can’t use are skipped during thesearch_pathlookup, so the table looks missing. Qualify the name and you getpermission denied for schemainstead. - The table doesn’t exist yet: a migration that hasn’t run, failed, or ran against another database; a table created in another session that hasn’t committed; another session’s temporary table.
How to fix it
Find where the table really is
Search every schema, ignoring case:
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_name ILIKE 'customers';
information_schema lists only tables you have some privilege on. In psql, \dt *.customers lists
the tables called customers in every schema; it folds the pattern to lower case, so write
\dt *."Customers" for a capitalised name (see psql commands). No rows at all?
Check where you are:
SELECT current_database(), current_user, current_schema();
SHOW search_path;
Quote names that have capitals
If the table shows up as Customers with a capital, quote it exactly, every time:
SELECT * FROM "Customers";
To stop quoting, rename it once to lower case (update any code that already quotes it):
ALTER TABLE "Customers" RENAME TO customers;
Qualify the schema, or add it to the search path
SELECT * FROM billing.invoices;
-- for this session
SET search_path = billing, public;
-- for every new session of one role
ALTER ROLE app SET search_path = billing, public;
ALTER ROLE … SET takes effect at the next connection, not in sessions already open.
Use the sequence’s real name
Don’t guess a sequence name from the table name. Ask for it:
SELECT pg_get_serial_sequence('invoices', 'id');
SELECT nextval(pg_get_serial_sequence('invoices', 'id'));
Grant access to the schema
If the table is in a schema your role can’t use, the owner or a superuser grants it:
GRANT USAGE ON SCHEMA billing TO app;
GRANT SELECT ON ALL TABLES IN SCHEMA billing TO app;
After that a missing table privilege shows up as permission denied, not as “does not exist”.
Reproduce it
On PostgreSQL 18.6, in a scratch schema:
CREATE TABLE "Customers" (id int PRIMARY KEY, name text);
SELECT * FROM Customers;
ERROR: relation "customers" does not exist
LINE 1: SELECT * FROM Customers;
^
SELECT * FROM "Customers"; works. The opposite mistake fails the same way: the table invoices is
lower case, and the quoted name has a capital:
ERROR: relation "Invoices" does not exist
LINE 1: SELECT * FROM "Invoices";
^
A table that exists in seo_pgsql, queried with the default search path:
SET search_path = "$user", public;
SELECT * FROM invoices;
SELECT count(*) FROM seo_pgsql.invoices;
ERROR: relation "invoices" does not exist
LINE 1: SELECT * FROM invoices;
^
The qualified seo_pgsql.invoices works. A sequence that kept its old name after the table was
renamed from bills to invoices:
ERROR: relation "invoices_id_seq" does not exist
LINE 1: SELECT nextval('invoices_id_seq');
^
pg_get_serial_sequence('invoices', 'id') returned seo_pgsql.bills_id_seq. And as a role without
USAGE on the schema, the unqualified name “does not exist” while the qualified one is refused:
ERROR: relation "invoices" does not exist
ERROR: permission denied for schema seo_pgsql
PostgreSQL 14, 15, 16 and 17 print exactly the same messages.
In Inlet
The query editor completes table names from the live schema, so a name you pick from the list is one that exists on the database you’re connected to. When a statement fails, Inlet shows the error at the position the server reports. With your own Anthropic API key, Ask Claude (⌘L) can fix the failed statement; it sends the schema and the SQL, never rows.