Skip to content

Handling Prisma P2002 unique constraint violations

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.

Unverifiedno reproductions yetWhy this confidence?
Revision 1published by DevYou curationrevision history
Run the diagnosisEvidence and compatibility

Symptoms

The diagnostic path

7 steps. Every step is written out below in full — the interactive version simply follows the branches for you.

  1. 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?
  2. 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
  3. 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
  4. 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.

  5. Step 5 · FixCatch P2002 explicitly, or switch to upsert

    Catch the specific error code rather than letting it propagate as an unhandled exception.

    Read-onlyjavascript
    try {
      await prisma.user.create({ data: { email: input.email, name: input.name } });
    } catch (err) {
      if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
        return { error: "email already registered" };
      }
      throw err;
    }

    What happens next

    • passed step 6, Confirm duplicates are now handled without an unhandled exception
  6. 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
  7. Step 7 · EndResolved

    Duplicate submissions now return a handled response instead of crashing the request.

Sources

Why this confidence?

What would strengthen it: 6 more independent reproductions. Reproductions from 3 more distinct environments.