{"record":{"id":"fccf64a943b3a0cd","repo":"payloadcms/payload","slug":"value-must-be-unique-fccf64","errorCode":null,"errorMessage":"Value must be unique","messagePattern":"Value must be unique","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"packages/drizzle/src/upsertRow/handleUpsertError.ts","lineNumber":76,"sourceCode":"        if (match && match[1]) {\n          fieldName = match[1]\n        }\n      }\n    } else if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {\n      // SQLite - extract from message: \"UNIQUE constraint failed: table.field[, table.field2, ...]\"\n      const regex = /UNIQUE constraint failed: ([^.]+)\\.([^.,]+)/\n      const match: string[] = error.message?.match(regex)\n      if (match && match[2]) {\n        if (adapter.fieldConstraints[tableName]) {\n          fieldName = adapter.fieldConstraints[tableName][`${match[2]}_idx`]\n        }\n        if (!fieldName) {\n          fieldName = match[2]\n        }\n      }\n    }\n\n    throw new ValidationError(\n      {\n        id,\n        collection: collectionSlug,\n        errors: [\n          {\n            message: req?.t ? req.t('error:valueMustBeUnique') : 'Value must be unique',\n            path: fieldName,\n            tableName,\n          },\n        ],\n        global: globalSlug,\n        req,\n      },\n      req?.t,\n    )\n  }\n\n  // Re-throw non-constraint errors","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/drizzle/src/upsertRow/handleUpsertError.ts#L58-L94","documentation":"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.","triggerScenarios":"An upsert (createOrUpdate) where the incoming row collides with an existing row on a unique-indexed column, e.g. duplicate email, slug, or username.","commonSituations":"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.","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."],"exampleFix":"// before\nawait payload.create({ collection: 'users', data: { email } })\n// after\ntry {\n  await payload.create({ collection: 'users', data: { email } })\n} catch (err) {\n  if (err.data?.errors?.some(e => e.message === 'Value must be unique')) {\n    return res.status(409).json({ error: 'email already in use' })\n  }\n  throw err\n}","handlingStrategy":"try-catch","validationCode":"async function isUnique(collection, field, value, payload) {\n  const { docs } = await payload.find({ collection, where: { [field]: { equals: value } }, limit: 1 })\n  return docs.length === 0\n}","typeGuard":null,"tryCatchPattern":"try {\n  await payload.create({ collection, data })\n} catch (err) {\n  const isUniqueErr = err?.data?.errors?.some(e => /unique/i.test(e.message))\n  if (isUniqueErr) return res.status(409).json({ error: 'duplicate value' })\n  throw err\n}","preventionTips":["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."],"tags":["upsert","unique","validation","database"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}