Quick answer
An INSERT/UPDATE tried to store a value that already exists in a unique or primary-key column. The DETAIL names the column and value. Use INSERT ... ON CONFLICT DO NOTHING or DO UPDATE (an upsert) instead of failing, and catch SQLSTATE 23505 in code. If it's the id after an import, the sequence is out of sync — reset it with setval.
The exact error string
ERROR: duplicate key value violates unique constraint "users_email_key"
DETAIL: Key (email)=(ada@example.com) already exists.
SQLSTATE: 23505
The constraint name (users_email_key) and the DETAIL tell you exactly what collided: column email, value ada@example.com. Postgres enforces uniqueness, so it rejects the duplicate row.
Fix 1: upsert with ON CONFLICT
If a duplicate is expected (re-running an import, "create or update"), tell Postgres what to do on conflict instead of letting it raise:
-- skip duplicates silently
INSERT INTO users (email, name) VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO NOTHING;
-- or update the existing row (true upsert)
INSERT INTO users (email, name) VALUES ('ada@example.com', 'Ada')
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;
EXCLUDED refers to the row you tried to insert. This is atomic and race-safe — unlike a check-then-insert, which can still collide between the check and the insert.
Fix 2: handle the violation in code
When a duplicate is a real client error (e.g. signing up with an existing email), catch SQLSTATE 23505 and return a clean response instead of a 500:
# Python / psycopg
import psycopg2
try:
cur.execute("INSERT INTO users (email) VALUES (%s)", (email,))
except psycopg2.errors.UniqueViolation:
conn.rollback()
return "Email already registered", 409 # Conflict
// node-postgres
try { await pool.query('INSERT INTO users(email) VALUES($1)', [email]); }
catch (e) { if (e.code === '23505') return res.status(409).send('exists'); throw e; }
Fix 3: the id sequence is out of sync
A very common variant: after a data restore or bulk import that set explicit id values, the SERIAL/IDENTITY sequence still points at a low number, so the next auto-generated id collides. Reset the sequence to the current max:
-- move the sequence past the highest existing id
SELECT setval(
pg_get_serial_sequence('users', 'id'),
(SELECT COALESCE(MAX(id), 0) FROM users)
);
-- next INSERT now gets max(id)+1 and no longer collides
Fix 4: find which constraint and columns
For a composite unique constraint, the conflict may span several columns. Inspect the table to see exactly what must be unique:
-- in psql: list constraints and indexes on the table
\d users
-- the unique constraint shows its column list, e.g.
-- "users_org_id_email_key" UNIQUE CONSTRAINT, (org_id, email)
Debugging checklist
- ✓ Read the
DETAIL— it names the column and the duplicate value - ✓ Expected duplicate? Use
ON CONFLICT DO NOTHING/DO UPDATE(upsert) - ✓ Real client error? Catch SQLSTATE
23505and return409 Conflict - ✓ Collision on
idafter an import? Reset the sequence withsetval - ✓ Don't check-then-insert — it has a race;
ON CONFLICTis atomic - ✓ Composite constraint?
\d tableshows which columns must be unique together
Frequently Asked Questions
What does 'duplicate key value violates unique constraint' mean?
An INSERT or UPDATE tried to store a value that already exists in a column protected by a unique constraint or primary key. The DETAIL line shows the conflicting column and value: Key (email)=(a@b.com) already exists. Postgres rejects the row to keep the column unique.
How do I insert without failing if the row exists?
Use INSERT ... ON CONFLICT. ON CONFLICT DO NOTHING skips the duplicate; ON CONFLICT (col) DO UPDATE SET ... performs an upsert (insert or update). This is atomic and race-safe, unlike checking for the row first and then inserting.
Why does it happen on the id column after importing data?
A serial/identity sequence is out of sync. If rows were inserted with explicit ids (a restore or bulk import), the sequence still points at a low number, so the next auto id collides with an existing row. Fix it with setval to the current max id: SELECT setval(pg_get_serial_sequence('t','id'), (SELECT max(id) FROM t)).
How do I catch the error in code?
Catch SQLSTATE 23505 (unique_violation). In psycopg use psycopg2.errors.UniqueViolation; in node-postgres check err.code === '23505'. Catching it lets you return a clean 409 Conflict instead of a 500, or fall back to an update.
Should I check if the row exists first?
Prefer ON CONFLICT. A check-then-insert has a race condition: between your SELECT and your INSERT another transaction can insert the same key, and you still get the violation. ON CONFLICT does the check and the insert atomically inside the database.
Which constraint was violated?
The error names it: violates unique constraint "users_email_key". The DETAIL gives the column and value. Look up the constraint with \d tablename in psql to see which columns it covers — composite unique constraints span more than one column.
More backend & build errors
Browse the full reference for Node.js, Python, Docker, and database errors — exact message, cause, and fix.