payloadcms/payload · error · ValidationError

Value must be unique

Error message

Value must be unique

What it means

Thrown as a structured `ValidationError` by `handleError` when MongoDB returns a duplicate-key error (E11000). The handler extracts the offending field from `error.keyValue` (or parses the message), strips any locale suffix from the path, and re-throws a per-field validation error so the API responds with a clean validation failure instead of a 500. The message resolves to `req.t('error:valueMustBeUnique')` when a translator is present, else the literal 'Value must be unique'.

Source

Thrown at packages/db-mongodb/src/utilities/handleError.ts:66

  if (!error || typeof error !== 'object') {
    throw error
  }

  // Handle uniqueness error from MongoDB
  if ('code' in error && error.code === 11000) {
    let path: null | string = null

    if ('keyValue' in error && error.keyValue && typeof error.keyValue === 'object') {
      path = Object.keys(error.keyValue)[0] ?? ''
    } else if ('message' in error && typeof error.message === 'string') {
      path = extractFieldFromMessage(error.message)
    }

    if (path) {
      path = stripLocaleFromPath(path, req)
    }

    throw new ValidationError(
      {
        collection,
        errors: [
          {
            message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',
            path: path ?? '',
          },
        ],
        global,
      },
      req?.t,
    )
  }

  // eslint-disable-next-line @typescript-eslint/only-throw-error
  throw error
}

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Choose a different value for the flagged field (path is in the error).
  2. Pre-check uniqueness before writing when contention is likely.
  3. Remove the `unique: true` flag or the underlying unique index if duplicates are acceptable.
  4. Surface the ValidationError to the UI and prompt the user to change the value.

Example fix

// before
await payload.create({ collection: 'users', data: { email: 'taken@x.com' } })
// after
const exists = await payload.find({ collection: 'users', where: { email: { equals: 'taken@x.com' } }, limit: 1 })
if (exists.docs.length) throw new Error('email taken')
await payload.create({ collection: 'users', data: { email: 'new@x.com' } })
Defensive patterns

Strategy: try-catch

Validate before calling

async function assertUnique(payload, collection, field, value) {
  const { docs } = await payload.find({ collection, where: { [field]: { equals: value } }, limit: 1 })
  if (docs.length) throw new Error(`${field} already in use`)
}

Type guard

const isValidationError = (e) =>
  e?.name === 'ValidationError' && Array.isArray(e.data?.errors)

Try / catch

try { await payload.create({ collection, data }) }
catch (e) {
  if (isValidationError(e) && e.data.errors.some(x => /unique/i.test(x.message))) {
    return { ok: false, field: e.data.errors[0].path }
  }
  throw e
}

Prevention

When it happens

Trigger: A `create` or `update` (or REST POST/PATCH) sets a field that carries a unique index to a value that already exists in the collection. The unique constraint can come from `unique: true` on a field or a manually-created unique index.

Common situations: Unique field collision on email/username/slug; seeding duplicate data; a race where two concurrent writes pick the same value; a previously-deleted doc whose unique value was retained by a soft-delete or a stale index.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/e3b34ed8f6a163f0. Report an issue: GitHub.