Covers recognising that P2002 is Postgres's unique constraint doing its job, correctly, and the two idiomatic ways to handle an expected duplicate. Does not cover tracing where an unexpected duplicate came from (a retry without an idempotency key, a double-fired webhook, a bad backfill) — this playbook only gets you to recognising which situation you're in.
`PrismaClientKnownRequestError` thrown from create() or update()
Message: "Unique constraint failed on the fields: (`email`)"
`code: 'P2002'` on the caught error
The diagnostic path
7 steps, exactly as this revision published them.
Step 1 · StartPrismaClientKnownRequestError P2002
Prisma's create() (or update()) was rejected because the row it tried to write conflicts with a unique index or constraint. This is Postgres's own uniqueness check firing — Prisma is just the messenger.
What happens next
passed → step 2, Is this an expected duplicate, or a bug in how the record is being created?
Step 2 · TestIs this an expected duplicate, or a bug in how the record is being created?
Check whether the request is a legitimate retry or double-submission (a form double-click, a client retrying without an idempotency key) versus code that shouldn't be able to produce a duplicate at all.
What happens next
passed → step 3, Prisma is correctly enforcing the constraint — handle it instead of preventing it
failed → step 4, An unexpected duplicate means something upstream is wrong
unknown → step 4, An unexpected duplicate means something upstream is wrong
Step 3 · Root causePrisma is correctly enforcing the constraint — handle it instead of preventing it
A SELECT-then-INSERT check in application code cannot prevent this under concurrency: two requests can both pass the SELECT before either INSERTs, a classic check-then-act race. The constraint violation is Postgres doing exactly what it's for — the fix is to catch the specific error code and respond appropriately, or use upsert.
What happens next
passed → step 5, Catch P2002 explicitly, or switch to upsert
Step 4 · Root causeAn unexpected duplicate means something upstream is wrong
If a duplicate genuinely shouldn't be possible here, check whether the caller retries without an idempotency key, whether a migration/backfill inserted the conflicting row, or whether two code paths (e.g. a webhook handler firing twice for the same event) can both attempt to create the same logical entity. Tracing the specific upstream duplicate is outside this playbook — it only gets you to recognising that P2002 means one already exists.
Step 5 · FixCatch P2002 explicitly, or switch to upsert
Catch the specific error code rather than letting it propagate as an unhandled exception.
passed → step 6, Confirm duplicates are now handled without an unhandled exception
Step 6 · Verify the fixConfirm duplicates are now handled without an unhandled exception
Re-run the same create() request twice with the same unique field and confirm the second attempt returns the handled response rather than an uncaught P2002.
What happens next
passed → step 7, Resolved
failed → step 4, An unexpected duplicate means something upstream is wrong
unknown → step 4, An unexpected duplicate means something upstream is wrong
Step 7 · EndResolved
Duplicate submissions now return a handled response instead of crashing the request.
What would strengthen it: 6 more independent reproductions. Reproductions from 3 more distinct environments.
This counts only what was recorded against revision 1 itself. Nothing reported against another revision is included here — see the revision history for why.