Skip to content
Nilay Kabariya

Entry No.054·Database·Updated ·4 min read

Drizzle "Failed query" error: find the real cause

Drizzle shows the SQL and params but not why it failed. Five causes reproduced, why e.code stopped working in 0.44, and the migration version.

by Nilay#drizzle#postgres#nodeDATABASE

The error, verbatim

Jump to the fix ↓
DrizzleQueryError: Failed query: insert into "users" ("id", "email", "name") values (default, $1, $2)
params: a@b.c,A again

Tested on

drizzle-orm
0.45.4 (latest), 0.44.0, 0.43.1
drizzle-kit
0.31.11
Database
Postgres 17 (pg 8, postgres.js 3) and PGlite 0.5.8
Node / OS
24.14.0 / Windows 11 Pro

✓ Re-verified on drizzle-orm 0.45.4 (Linux): 4/4 checks still hold · results

Contents
  1. Read the cause01
  2. Why you only see “Failed query”02
  3. e.code === '23505' stopped working in 0.4403
  4. The cause with node-postgres and postgres.js04
  5. “applying migrations…” then Failed query: CREATE TABLE05
  6. What didn’t work06
  7. How this was tested07

Failed query: is Drizzle telling you which SQL it sent, not what went wrong. Since drizzle-orm 0.44.0, every database error is wrapped in a DrizzleQueryError whose message is only the query and its parameters. The reason, the error from Postgres itself, is attached as cause. On 0.43.1 the same insert threw the Postgres error directly.

I reproduced five common causes on drizzle-orm 0.45.4. Every one produced the same kind of message, so the SQL alone doesn’t tell them apart.

Read the cause

The Postgres error, with its message and its code, is on e.cause:

try {
  await db.insert(users).values({ email, name });
} catch (e) {
  console.log(e.cause?.message, e.cause?.code);
}

These are the five, with what the wrapper said and what e.cause said:

What happened Failed query: showed e.cause said Code
Table not created yet select "id", "email", "name" from "users" relation "users" does not exist 42P01
Duplicate unique value insert into "users" ... duplicate key value violates unique constraint "users_email_key" 23505
Missing required column insert into "users" ..., params: x@y.z, null value in column "name" of relation "users" violates not-null constraint 23502
Foreign key points nowhere insert into "posts" ..., params: 999,Hi insert or update on table "posts" violates foreign key constraint "posts_author_id_fkey" 23503
Wrong type in a filter select ... where "posts"."id" = $1, params: not-a-uuid invalid input syntax for type uuid: "not-a-uuid" 22P02

The params line can hint at it (an empty value after the last comma, 999 for an author ID), but only the cause says it outright.

Why you only see “Failed query”

The cause isn’t hidden from Node: console.error(e) prints it, further down after the stack trace, as cause: error: relation "nope" does not exist. It disappears wherever only the message is used: e.message, String(e), an error toast, a JSON response like { error: e.message }, or an error tracker that groups by message. All of those show the SQL and nothing else, which is why the same “Failed query” seems to have no explanation.

e.code === '23505' stopped working in 0.44

This is the breaking part of the change. A common way to turn a duplicate email into a friendly “that email is taken” message is to check the Postgres error code. I ran the same duplicate insert on three versions:

drizzle-orm Thrown e.code e.cause.code
0.43.1 the Postgres error 23505 —
0.44.0 DrizzleQueryError undefined 23505
0.45.4 DrizzleQueryError undefined 23505

So after upgrading, a check on e.code is silently never true: no error, just the friendly message never shows, and the raw failure reaches the user instead.

Unwrap first, then check the code:

import { DrizzleQueryError } from 'drizzle-orm/errors';

const pgError = (e: unknown) => (e instanceof DrizzleQueryError ? e.cause : e) as { code?: string } | undefined;

try {
  await db.insert(users).values({ email, name });
} catch (e) {
  if (pgError(e)?.code === '23505') return { error: 'That email is already registered.' };
  throw e;
}

Import it from drizzle-orm/errors. On 0.45 it’s also exported from drizzle-orm itself, but on 0.44.0 importing it from there stops the app from loading. 0.43.1 has no drizzle-orm/errors at all, and doesn’t need it, since it throws the Postgres error directly.

The cause with node-postgres and postgres.js

Against a real Postgres 17 server, both popular drivers gave the same wrapper and the same e.cause.code, but the error inside differs, and one field name differs:

node-postgres (pg) postgres.js
e DrizzleQueryError DrizzleQueryError
e.cause DatabaseError PostgresError
e.cause.code 23505 23505
e.cause.detail Key (email)=(a@b.c) already exists. same
Constraint name e.cause.constraint e.cause.constraint_name

detail is the most useful line for a duplicate: it names the column and the value. If you tell users which field is taken based on the constraint name, read the right property for your driver: constraint was undefined on postgres.js, and constraint_name was undefined on pg.

“applying migrations…” then Failed query: CREATE TABLE

The migration version of this error usually means the table already exists, created by drizzle-kit push or by hand, outside the migrations Drizzle keeps track of. I created users directly, then ran the generated migration through Drizzle’s migrate():

DrizzleQueryError: Failed query:
CREATE TABLE "users" (
	"id" serial PRIMARY KEY NOT NULL,
	"email" text NOT NULL,
	...
cause: relation "users" already exists (code 42P07)

With the PGlite driver, drizzle-kit migrate itself printed nothing but its spinner and exited with code 1. Only running the migration from code showed the cause. If your migrate step fails silently, run it once through migrate() in a small script to see the error.

Don’t mix push and migrate on the same database. On a database that only ever received migrations, the same migration applied cleanly.

I didn’t test moving an existing pushed database onto migrations. In development, recreating the database and running the migrations from the start is the simple way out.

What didn’t work

How this was tested

drizzle-orm 0.45.4 (the latest), 0.44.0 and 0.43.1 with the PGlite 0.5.8 driver (Postgres compiled to WebAssembly), and 0.45.4 with node-postgres 8 and postgres.js 3 against a Postgres 17 server in Docker. Each case was a fresh in-memory database with a users table (unique email, required name) and a posts table with a foreign key to it. Migrations were generated with drizzle-kit 0.31.11 and applied with drizzle-kit migrate and with migrate() from drizzle-orm/pglite/migrator. Node 24.14.0 on Windows 11, October 10, 2026.

The code is public, so you can run it yourself: drizzle-failed-query in nk-repro.

— N.K., end of entry No.054

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.045

    Supabase error 42501: new row violates row-level security policy

    Supabase's 42501 has two messages and seven causes. Each reproduced on a local stack, plus the October 30 change that breaks new tables.

    > {

    DATABASE6 min
  2. No.079

    Prisma P2002 on Prisma 7: e.meta.target is now undefined

    On Prisma 7, P2002 names the constraint on Postgres and e.meta.target is undefined on Postgres and SQLite. Where the field went, and the upsert race.

    > PrismaClientKnownRequestError:

    DATABASE4 min
  3. No.078

    Postgres password authentication failed, but the password is right

    Changed POSTGRES_PASSWORD in docker-compose and the old one still works? The volume keeps it. Five causes of this Postgres error, reproduced with postgres:18.

    > psql: error: connection to server at "localhost" (::1), port 5432 failed: FATAL: password authentication failed for user "postgres"

    DATABASE5 min
  4. No.053

    Prisma P1012: "url is no longer supported in schema files"

    Prisma 7 rejects the url line every older tutorial uses. Reproduced on 7.10.0, plus directUrl, the .env trap and the adapter error that comes next.

    > Error: Prisma schema validation - (get-config wasm)

    DATABASE3 min

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text