Drizzle db.query drops pgSchema: relation does not exist
Get db.query.findMany() and findFirst() reading your custom Postgres schema again: which drizzle-orm versions drop the schema, why, and three tested ways out.
TL;DR#
On drizzle-orm 0.39.1 up to 0.40.1, a relational query such as db.query.user.findMany() on a table declared with pgSchema('app_one') builds select "id", "name" from "user", without the schema, and PostgreSQL answers relation "user" does not exist. It happens when the table's key in your Drizzle schema object is the same as its database name. db.select().from(user) is not affected. Upgrade to drizzle-orm 0.41.0 or later, whose release notes list the fix. If you cannot upgrade yet, give the table a key that differs from its database name (appUser for the user table), which makes Drizzle write the full schema-qualified name.
The report is drizzle-orm issue #4060, opened on drizzle-orm 0.39.1 and drizzle-kit 0.30.4. Its schema declares the table inside a custom Postgres schema:
export const user = pgSchema('app_one').table('user', {
id: integer('id').generatedAlwaysAsIdentity().primaryKey(),
name: text('name'),
});db.query.user.findMany() then runs this SQL, which looks for user on the search path instead of in app_one:
select "id", "name" from "user"A second reporter in the same thread hit it on drizzle 0.40.0 with a table named paypal in a schema named temp, on Bun 1.2.4; their driver reported it as a PostgresError:
relation "paypal" does not exist
PostgresError: relation "paypal" does not existWith pg, the reproduction below got the same message as a DatabaseError.
Which versions build the wrong SQL#
The behaviour was reproduced on 20 October 2026 with the official postgres:16-alpine image (PostgreSQL 16.15), node:22-alpine (Node v22.23.3) and pg 8.13.1. The database holds one table, app_one."user", with one row. The script declares that table twice: once under the key user (the case from the issue) and once under the key appUser, then prints the SQL from .toSQL() and runs both relational queries.
The broken versions, 0.39.1 and 0.40.1:
--- drizzle-orm 0.39.1
select().from(user) : select "id", "name" from "app_one"."user"
query.user.findMany() : select "id", "name" from "user"
query.appUser.findMany(): select "id", "name" from "app_one"."user" "appUser"
query.user.findMany() -> DatabaseError: relation "user" does not exist
query.appUser.findMany() -> 1 row(s)--- drizzle-orm 0.40.1
select().from(user) : select "id", "name" from "app_one"."user"
query.user.findMany() : select "id", "name" from "user"
query.appUser.findMany(): select "id", "name" from "app_one"."user" "appUser"
query.user.findMany() -> DatabaseError: relation "user" does not exist
query.appUser.findMany() -> 1 row(s)The version just before, 0.39.0, and the fixed 0.41.0:
--- drizzle-orm 0.39.0
select().from(user) : select "id", "name" from "app_one"."user"
query.user.findMany() : select "id", "name" from "app_one"."user"
query.appUser.findMany(): select "id", "name" from "app_one"."user" "appUser"
query.user.findMany() -> 1 row(s)
query.appUser.findMany() -> 1 row(s)--- drizzle-orm 0.41.0
select().from(user) : select "id", "name" from "app_one"."user"
query.user.findMany() : select "id", "name" from "app_one"."user" "user"
query.appUser.findMany(): select "id", "name" from "app_one"."user" "appUser"
query.user.findMany() -> 1 row(s)
query.appUser.findMany() -> 1 row(s)drizzle-orm 0.38.4, the version two commenters on the issue went back to or stayed on, behaves like 0.39.0. drizzle-orm 0.45.4, the latest tag on npm on 20 October 2026, behaves like 0.41.0. Three facts follow from these runs: the core query builder (db.select().from(user)) was right on every version; the relational query lost the schema only when its key matched the table name; and a key that differs from the name worked even on the broken versions.
Why it happens#
Two changes, one release apart, produced the bug.
0.39.1 stopped prefixing aliases with their schema. The 0.39.1 release notes list "Fixed sql builder prefixing aliased views and tables with their schema". In the source, the SQL builder now writes only the bare name when a table object is an alias (sql.ts at 0.39.1, line 184):
if (is(chunk, Table)) {
const schemaName = chunk[Table.Symbol.Schema];
const tableName = chunk[Table.Symbol.Name];
return {
- sql: schemaName === undefined
+ sql: schemaName === undefined || chunk[IsAlias]
? escapeName(tableName)
: escapeName(schemaName) + '.' + escapeName(tableName),That is correct for an alias that appears in a column reference ("appUser"."id"), but it means the FROM clause must write the full name itself.
The FROM clause only did that when the alias had a different name. The Postgres dialect writes "schema"."table" "alias" in a separate function, and on 0.39.1 to 0.40.1 it decided whether a table was an alias by comparing the alias name with the original table name. Relational Queries v1 alias the top-level table with its key in your schema object (query.ts at 0.40.1, line 124), and that key comes from iterating the object you pass as schema to drizzle() (relations.ts at 0.40.1, line 438). With import * as schema from './schema', the key is the export name. When the key is user and the table is user, both names are equal, the dialect treats the alias as a plain table, and the SQL builder then prints the alias without its schema. That gives from "user".
0.41.0 checks the alias flag instead of the names. The 0.41.0 release notes list "Fixed custom schema tables querying in RQBv1: #4060", shipped in the bundled pull request #4293. The change in the dialect, from the 0.40.1 to 0.41.0 comparison:
- if (is(table, Table) && table[Table.Symbol.OriginalName] !== table[Table.Symbol.Name]) {
+ if (is(table, Table) && table[Table.Symbol.IsAlias]) {
let fullName = sql`${sql.identifier(table[Table.Symbol.OriginalName])}`;
if (table[Table.Symbol.Schema]) {
fullName = sql`${sql.identifier(table[Table.Symbol.Schema]!)}.${fullName}`;That is why 0.41.0 prints from "app_one"."user" "user": the table is aliased to its own name, and the schema is back.
Tables declared with pgTable live in the default schema and have no schema name to lose, so the bug only shows with pgSchema(...).table(...). The Drizzle docs cover both: custom schemas and the relational query API.
Fix#
-
Check which drizzle-orm is installed. Only 0.39.1 up to 0.40.1 were affected in the runs above.
bashnpm ls drizzle-orm -
Upgrade drizzle-orm to 0.41.0 or later. The latest stable line was 0.45.4 when this was tested.
bashnpm install drizzle-orm@0.45.4Upgrading from 0.38.x crosses 0.39.0, whose type changes have their own reported regression: generic helpers that pass a table or subquery to
.from()can fail to compile with TS2345TableLikeHasEmptySelection. If the build now fails there, that page has the call-site workaround. -
If you must stay on 0.39.1 to 0.40.1 for now, rename the key so it differs from the database table name. The table name in
.table('user', …)stays the same, so no migration is needed.tsimport { integer, pgSchema, text } from 'drizzle-orm/pg-core'; export const appOne = pgSchema('app_one'); // key "appUser", database table still app_one."user" export const appUser = appOne.table('user', { id: integer('id').generatedAlwaysAsIdentity().primaryKey(), name: text('name'), });Then query it under the new key:
db.query.appUser.findMany(). Everydb.query.usercall has to change too, which is why the upgrade is the better fix. If you build the schema object by hand rather than withimport * as schema, the key in that object is what counts, not the export name. -
A narrower stop-gap for one hot path: the core query builder kept the schema on every version tested, so
db.select().from(user)works on the broken versions as it is. It does not give you the nestedwithresults ofdb.query.
Verify the fix#
Print the SQL before running it. .toSQL() works on a relational query as it does on a select:
const q = db.query.user.findFirst({
where: (t, { eq }) => eq(t.id, 1),
columns: { name: true },
});
console.log(q.toSQL().sql);
console.log(await q);On 0.40.1, the reproduction printed a FROM without the schema, and the query failed:
--- drizzle-orm 0.40.1
findFirst sql: select "name" from "user" where "user"."id" = $1 limit $2
DatabaseError: relation "user" does not existOn 0.41.0, the same code printed the schema-qualified name with the alias, and returned the row:
--- drizzle-orm 0.41.0
findFirst sql: select "name" from "app_one"."user" "user" where "user"."id" = $1 limit $2
result: {"name":"Ada"}The "user"."id" in the WHERE clause is the alias, not the table, so it stays unqualified on both versions. What matters is the FROM clause: once it reads "app_one"."user" "user", the fix is in.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When the error persists after upgrading#
If the SQL from .toSQL() now shows the schema but PostgreSQL still says the relation does not exist, the bug above is no longer the cause. Check the physical table: \dt app_one.* in psql lists what is really in the schema, and a table created with mixed case needs the exact quoted spelling. The Prisma 42P01 page walks through that case for raw SQL, where PostgreSQL folds an unquoted name to lowercase; the catalogue checks apply to Drizzle as well.
A deep with tree is the next place where Drizzle's relational aliases meet a PostgreSQL limit. Relational Queries v1 build nested aliases by joining relation keys, and past 63 bytes PostgreSQL truncates them; the symptom is table name "..." specified more than once, a different error with a different fix.
Teams that pinned drizzle-orm 0.38.4 to avoid this bug can move straight to the current 0.45 line: in the runs above, both kept the schema in relational queries, and only the versions between 0.39.1 and 0.40.1 dropped it. Check the SQL with .toSQL() once after the upgrade, and the relation does not exist error from a pgSchema table should not come back for this reason.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
drizzle-kit migrate: relation "users" already exists
Get drizzle-kit migrate working on a database that drizzle-kit push created: why the first CREATE TABLE runs again, how to mark the old migrations as applied, and why IF NOT EXISTS only hides the problem.
Drizzle: Offset is outside the bounds of the DataView
Make findFirst({ with }) return your PostGIS points again: why only nested relations crash, and three fixes tested against PostGIS 3.6.
Drizzle: Cannot destructure property 'resourceArn' fix
Get drizzle-kit studio and migrate talking to Aurora through the AWS Data API again, and know which error comes next on current releases.


