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.
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
profilesrow, 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
useEffectthat 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
- Read the constraint name and the
Key (...)detail to see which column and value collided. - If the row can already exist, switch to
update, or toupsertwithonConflictset to that column. With RLS on,upsertalso needs anUPDATEpolicy. - If a trigger creates profiles, remove the app’s own profile insert and update the row instead.
- For values people choose, check for
error.code === '23505'and show “That username is taken.” - If numeric IDs collide after an import, reset the counter with
select setval(pg_get_serial_sequence('todos', 'id'), (select max(id) from todos));. - 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;
}Related errors
All errorsNew 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 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 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 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.