PostgreSQL error: no unique or exclusion constraint matching the ON CONFLICT specification

ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

INSERT ... ON CONFLICT needs an arbiter: a unique (or exclusion) constraint that defines what "conflict" means. The conflict target you wrote must match one exactly — same column set, same expression, same partial predicate. No match, no upsert.

What this error means

PostgreSQL does not guess which constraint you mean. ON CONFLICT (email) requires a unique index on exactly (email):

Common causes

How to diagnose it

\d users   -- elenca indici e vincoli con la loro definizione esatta

Compare the conflict target in the failing INSERT against the index definitions, checking columns, expressions and WHERE clauses literally.

How to fix it

-- Manca il vincolo → crealo (in produzione: CONCURRENTLY)
CREATE UNIQUE INDEX CONCURRENTLY users_email_key ON users (email);

-- Indice su espressione → il target deve ripeterla:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (lower(email)) DO NOTHING;

-- Indice parziale → il target deve ripetere il predicato:
INSERT INTO users (email) VALUES ('[email protected]')
ON CONFLICT (email) WHERE deleted_at IS NULL DO NOTHING;

Alternatively name the constraint directly — ON CONFLICT ON CONSTRAINT users_email_key — though the column form survives constraint renames better.

🔍 The error ON CONFLICT exists to prevent: duplicate key value violates unique constraint.
🔍 Related reading: Choosing the right index — unique, partial and expression indexes.