Skip to content
Nilay Kabariya

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

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.

by Nilay#prisma#postgresDATABASE

The error, verbatim

Jump to the fix ↓
PrismaClientKnownRequestError:
Invalid `prisma.user.create()` invocation:

Unique constraint failed on the fields: (`email`)

Tested on

prisma
7.10.1 (and 6.19.3 for comparison)
Adapters
@prisma/adapter-pg, @prisma/adapter-better-sqlite3 7.10.1
Databases
Postgres 18 (Docker), SQLite
Node / OS
24.21.0 / Ubuntu 24.04

✓ Last re-run on GitHub Actions · see the run · all repros

Contents
  1. Find which field collided01
  2. Why your code wrote it twice02
  3. P2002 on the id: the sequence is behind03
  4. P2002 from upsert: two requests at once04
  5. How this was tested05

If your “that email is already taken” message stopped appearing after upgrading to Prisma 7, this is why: on Prisma 7.10.1, e.meta.target was undefined on both Postgres and SQLite, and on Postgres the message names the constraint (User_email_key) instead of the field. The error code is still P2002, so nothing else looks wrong. P2002 itself means the database refused a row because a @unique or @id column already has that value.

Prisma 7 made the first question harder. On the same duplicate email, three setups gave three different results:

Setup Last line of the message e.meta.target
Prisma 6.19.3, Postgres Unique constraint failed on the fields: (`email`) ["email"]
Prisma 7.10.1, Postgres (adapter-pg) Unique constraint failed on the constraint: `User_email_key` undefined
Prisma 7.10.1, SQLite (adapter-better-sqlite3) Unique constraint failed on the fields: (`email`) undefined

So if your error handler reads e.meta.target to tell the user “that email is taken”, it stopped working on Prisma 7, on both databases, and the error’s code is still P2002 so nothing else looks wrong.

Find which field collided

Prisma 7 runs every query through a driver adapter, and the details moved into the adapter’s error. The field or constraint name is here:

e.meta?.driverAdapterError?.cause?.constraint

On Postgres that object was { "index": "User_email_key" }, the constraint’s name. On SQLite it was { "fields": ["email"] }. A helper that works on both, and on Prisma 6:

import { Prisma } from './generated/prisma/client';

const isUnique = (e: unknown) =>
  e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002';

const which = (e: any) => {
  const c = e?.meta?.driverAdapterError?.cause?.constraint;
  return c?.fields?.join(',') ?? c?.index ?? e?.meta?.target;
};

Postgres names a unique constraint <Model>_<field>_key by default, and the primary key <Model>_pkey, so the field is still easy to read from the name.

Why your code wrote it twice

I hit P2002 five ways on one User model with a unique email:

What the code did Postgres SQLite
create with an email that exists P2002 P2002
update to an email another row has P2002 P2002
create after rows were inserted with explicit ids P2002 on User_pkey worked
3 parallel upsert calls for the same new email, 20 times 40 of 60 failed 0 of 60 failed
3 parallel “findUnique, then create if missing”, 20 times 40 of 60 failed 40 of 60 failed

Also worth knowing: createMany with the same email twice in one call failed with P2002 and inserted nothing, not even the first row. And A@x.com next to a@x.com did not fail: the check is case-sensitive, so duplicates by case get through.

P2002 on the id: the sequence is behind

If the message names User_pkey (or fields: (`id`)), your new row got an id that already exists. On Postgres, @default(autoincrement()) takes the next number from a sequence. Rows inserted with an explicit id, by a seed script, an import or a copied dump, don’t move the sequence. So the next normal create asks for id 1, which a seed row already has.

SQLite picks the next id from the highest one in the table, so the same steps worked there.

Move the sequence past the highest id:

SELECT setval(pg_get_serial_sequence('"User"', 'id'), (SELECT MAX(id) FROM "User"));

From Prisma, run it with prisma.$executeRawUnsafe(...), or in psql. Use your own table and column names. Prisma’s table names keep their capital letters, so they need the double quotes.

P2002 from upsert: two requests at once

This one confuses people, because upsert sounds like it can’t create a duplicate. On Postgres, 40 of my 60 parallel upserts failed with P2002: the requests all checked, all saw no row, and all tried to insert. SQLite ran the same test with 0 failures.

The same race hits any “check, then create” code, on both databases: a form submitted twice, a webhook delivered twice, two tabs signing up at once.

The database’s unique constraint is what keeps the data right, so let it, and treat P2002 as “someone else just created it”:

async function upsertUser(email: string) {
  try {
    return await prisma.user.upsert({ where: { email }, update: {}, create: { email } });
  } catch (e) {
    if (isUnique(e)) return prisma.user.findUniqueOrThrow({ where: { email } });
    throw e;
  }
}

When you only need the rows to exist and don’t need them back, createMany with skipDuplicates: true skips duplicates instead of failing:

How this was tested

Ubuntu 24.04 on a GitHub Actions runner, Node 24.21.0, October 18, 2026. Prisma 7.10.1 with @prisma/adapter-pg against Postgres 18 in Docker, and with @prisma/adapter-better-sqlite3 against a SQLite file, both on the same User model. Prisma 6.19.3 without an adapter, on the same Postgres, for the comparison. The parallel tests sent 3 requests at once for each of 20 new emails. Every message above is the real output, from the test script.

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

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. 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
  2. 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
  3. No.050

    Prisma P1001: Can't reach database server at localhost:5432

    P1001 prints the same words for five different problems, Docker and Supabase included. Each one reproduced, plus a one-line check that tells them apart.

    > Error: P1001: Can't reach database server at `localhost:5432`

    DATABASE5 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