Unique constraint failed on the fields (Prisma P2002)

Filed under Supabase & databases

Most likely causeAbout 10 min · 6 steps

Your app tried to create or update a row with a value that a unique field already holds — most often a repeat signup with the same email, or a seed script run a second time.

Read which field the error names — that's the collision
Look up the row that already holds that value
Use upsert, or catch P2002 and say 'already exists' instead of crashing

Fastest route

Give your AI this prompt

Copy it, replace the bracketed part with your error, and send it.

Preview the prompt
My app uses Prisma and a write is failing with: "Unique constraint failed on the fields: ([the field from the error])" — error code P2002. It happens when [signing up / running the seed / importing — pick what fits].

Please: 1) find the create call that produces this error and tell me which existing row it collides with and why that row exists, 2) decide with me whether this flow should update-or-create or reject duplicates, and implement that: upsert keyed on the unique field if it should update, or a caught P2002 that returns a clear "already exists" message if it should reject, 3) make the seed/setup scripts idempotent using upsert, and guard the form against double submission, 4) normalise the unique value (lowercase, trimmed) before writing.

Constraints: do not remove the unique constraint and do not delete the existing row to make the error go away. Make one change at a time and tell me how to test it, including running the same action twice.

Fix it yourself

Follow the repair

  1. Read the field the error names. P2002's details (the target in the error's meta) name the exact field or fields that collided. That tells you where to look: an email collision is a signup story; an external-ID collision is an import story. Start from the named field instead of re-reading your whole form.
  2. Find the row that already exists. Query the table for a row holding that value — in the Supabase table editor, Table Editor in the dashboard, or a quick findUnique in a scratch call. Nine times out of ten it is a legitimate earlier record: the same user, the seed's first run, yesterday's import. Knowing that turns this from "the database is broken" into a flow-design question.
  3. Match the code to your intent. If the flow means "create this if it's new, update it if it's already here" — profiles, settings, seed data, synced records — replace create with upsert, keyed on the unique field, saying what to write on create and what to refresh on update. If duplicates genuinely should be rejected (two accounts, one email), keep the create and go to the next step.
  4. Catch P2002 and answer like a product. Wrap the write and catch the error: if it is a PrismaClientKnownRequestError with code P2002, return the message a human should see — "An account with this email already exists" — with a sensible status, not a stack trace and a 500. The constraint still protects your data; your app just stops treating a predictable event as a catastrophe.
  5. Make repeats safe. Seeds and setup scripts: upsert every fixed row so the script can run any number of times. Forms: disable the submit button while a submission is in flight, and make retried requests carry the same intent rather than blindly creating again. Most P2002s in production are the same write arriving twice.
  6. Check for disguised duplicates. If the value "obviously" isn't in the table, compare carefully: most databases treat [email protected] and [email protected] as two different strings, and trailing spaces do the same trick. Normalise before saving — lowercase and trim emails and slugs at the point of write — and if the error persists, search case-insensitively for the twins that snuck in earlier.
Why this happensOptional explanation

A unique field is a promise the database enforces: no two rows may hold the same value. Emails, usernames, slugs, and external IDs (a Stripe customer ID, an auth user ID) are the classic ones. When your code asks Prisma to create a row that breaks that promise, the database refuses and Prisma reports the refusal as P2002, naming the fields involved.

AI-built apps trip this constantly because "create" is the verb they reach for in flows that are really "create, unless it exists." A signup form creates a profile; the auth provider's welcome hook also creates a profile; the second create collides. A user double-clicks, or a form retries after a slow network, and the same create runs twice milliseconds apart — the first succeeds, the second is P2002. Developers hit it too: seed scripts written with create work exactly once, then fail forever after. And imports and sync jobs re-offer records the database already has. In every case the collision is real; the only question is what your app meant to do about it.

  • Repeat signup: the same email registering or being provisioned twice, often by two different code paths.
  • Double submit: a retried request, a double click, or two racing calls creating the same row.
  • Second seed run: seed and setup scripts that create fixed rows without checking for them first.
  • Re-import: sync jobs offering an external ID the database already knows.

Quick answers

Should I just remove the unique constraint?

Almost never. P2002 is your database's guardrail working correctly — it stopped a duplicate. If the field genuinely should allow repeats, that's a schema design decision to make deliberately, not a fix for this error.

When should I use upsert instead of create?

upsert says 'create this if it's new, update it if it exists' in one call. Use it when your intent is genuinely create-or-update — signups, seeds, sync jobs. Don't use it to paper over a flow that should reject duplicates, like two accounts claiming one email.

The value looks unique — why did it still fail?

Case. Most databases treat '[email protected]' and '[email protected]' as different strings, so both pass a unique check. Normalise emails to lowercase before saving, and check for existing mixed-case duplicates if the error seems impossible.

One fix, every Friday.

One real error, explained in plain English, with the fix — in your inbox every Friday. No spam, no jargon, unsubscribe anytime.