Duplicate key value violates unique constraint

The app tried to save a row whose ID, email, or other unique value is already in the table, so Postgres rejected the copy.

Supabase and browser console
duplicate key value violates unique constraint "profiles_pkey"

Prompt to fix it

Saving fails with "duplicate key value violates unique constraint". Find the constraint named in the error and the code that writes that row. Work out why the row already exists: a sign-up trigger that already created it, an insert that should be an update, or a request that runs twice. Fix that cause: use update, or upsert with the right onConflict column, where the row may already exist, and show a clear message when someone picks a value that’s taken. Keep the unique constraint, and don’t catch and ignore the error.

Paste into Layerwise or any AI coding tool.

What it means

A unique constraint promises that no two rows share a value, such as an ID or a username. The quoted name says which one: profiles_pkey is the table’s primary key, and a name like profiles_username_key is a unique column. The detail line shows the value, for example Key (id)=(8f14e45f-ceea-467a-9575-6f2bfb3f0b9e) already exists.

In Supabase apps, the usual culprit is the profile row. Many starter templates add a trigger that creates a profiles row when someone signs up, and then the app’s onboarding screen inserts the same row again.

Pick the fix by intent. If the row may already exist, update it or use upsert. If the value has to stay unique, like a username, keep the constraint and show a friendly message when the error code is 23505.

Common causes

  • A sign-up trigger already created the profiles row, and the app inserts it again.
  • A save button always runs insert, even when it’s editing a row that already exists.
  • The same insert runs twice, from a double click or from a useEffect that React’s Strict Mode runs twice in development.
  • Someone picks an email or username that another account already uses.
  • Rows were imported with their own numeric IDs, so the ID counter now hands out numbers that are taken.

How to fix it

  1. Read the constraint name and the Key (...) detail to see which column and value collided.
  2. If the row can already exist, switch to update, or to upsert with onConflict set to that column. With RLS on, upsert also needs an UPDATE policy.
  3. If a trigger creates profiles, remove the app’s own profile insert and update the row instead.
  4. For values people choose, check for error.code === '23505' and show “That username is taken.”
  5. If numeric IDs collide after an import, reset the counter with select setval(pg_get_serial_sequence('todos', 'id'), (select max(id) from todos));.
  6. Disable the submit button while the request runs.

Example fix

// The sign-up trigger may have created this profile already.
const { error } = await supabase
  .from('profiles')
  .upsert({ id: user.id, username }, { onConflict: 'id' });

if (error?.code === '23505') {
  // Another profile already uses this username.
  setFormError('That username is taken.');
} else if (error) {
  throw error;
}
All errors
new row violates row-level security policy for table "todos"

New row violates row-level security policy

Postgres refused to save a row because no row-level security policy on that table lets the current user write it.

null value in column "user_id" of relation "todos" violates not-null constraint

Null value in column violates not-null constraint

A save left a required column empty, and that column has no default value for Postgres to fill in.

insert or update on table "todos" violates foreign key constraint "todos_user_id_fkey"

Insert or update on table violates foreign key constraint

A row points at a parent row that doesn’t exist, or something tried to delete a parent row that other rows still point at.

relation "todos" does not exist

Relation does not exist

The query names a table that Postgres can’t find in this database, usually because it was never created here or has a different name.

Build with an agent that checks its work

The Layerwise agent clicks through the live preview and reads the console, so it catches errors before you do.