payloadcms/payload · error · ValidationError
Value must be unique
Error message
Value must be unique
What it means
Thrown by handleUpsertError after a DB upsert fails with a UNIQUE constraint violation. The adapter parses the constraint-failure message (SQLite 'UNIQUE constraint failed: table.column' or Postgres unique-violation) to recover the offending field name via adapter.fieldConstraints, then re-throws as a Payload ValidationError with field-level details so the API returns a structured validation error instead of a raw DB error.
Source
Thrown at packages/drizzle/src/upsertRow/handleUpsertError.ts:76
if (match && match[1]) {
fieldName = match[1]
}
}
} else if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
// SQLite - extract from message: "UNIQUE constraint failed: table.field[, table.field2, ...]"
const regex = /UNIQUE constraint failed: ([^.]+)\.([^.,]+)/
const match: string[] = error.message?.match(regex)
if (match && match[2]) {
if (adapter.fieldConstraints[tableName]) {
fieldName = adapter.fieldConstraints[tableName][`${match[2]}_idx`]
}
if (!fieldName) {
fieldName = match[2]
}
}
}
throw new ValidationError(
{
id,
collection: collectionSlug,
errors: [
{
message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
path: fieldName,
tableName,
},
],
global: globalSlug,
req,
},
req?.t,
)
}
// Re-throw non-constraint errorsView on GitHub (pinned to 00c58b35c0)
Solutions
- Validate uniqueness in your application/validation hook before attempting the upsert (with a tolerance for races).
- Prepend a select to check existence and branch to update instead of relying on upsert to detect duplicates.
- Catch the ValidationError at the API boundary and return a 422 with the offending field path to the client.
Example fix
// before
await payload.create({ collection: 'users', data: { email } })
// after
try {
await payload.create({ collection: 'users', data: { email } })
} catch (err) {
if (err.data?.errors?.some(e => e.message === 'Value must be unique')) {
return res.status(409).json({ error: 'email already in use' })
}
throw err
} Defensive patterns
Strategy: try-catch
Validate before calling
async function isUnique(collection, field, value, payload) {
const { docs } = await payload.find({ collection, where: { [field]: { equals: value } }, limit: 1 })
return docs.length === 0
} Try / catch
try {
await payload.create({ collection, data })
} catch (err) {
const isUniqueErr = err?.data?.errors?.some(e => /unique/i.test(e.message))
if (isUniqueErr) return res.status(409).json({ error: 'duplicate value' })
throw err
} Prevention
- Validate uniqueness in a beforeChange hook (with race awareness) for better UX.
- Map the ValidationError field path back to the form input that caused it.
When it happens
Trigger: An upsert (createOrUpdate) where the incoming row collides with an existing row on a unique-indexed column, e.g. duplicate email, slug, or username.
Common situations: Two concurrent requests creating the same unique value; seeding/importing data with duplicate keys; a unique field that the client did not validate before submit.
Related errors
- Invalid database type given. Valid types are: ${Object.value
- Value must be unique
- The following path${results.length === 1 ? '' : 's'} cannot
- The following path${results.length === 1 ? '' : 's'} cannot
- The following field is invalid: ${name}
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/fccf64a943b3a0cd.
Report an issue: GitHub.