{"record":{"id":"7de1265939ff4f3e","repo":"payloadcms/payload","slug":"missing-collectionconfig-auth-loginwithusername","errorCode":null,"errorMessage":"Missing ${collectionConfig.auth.loginWithUsername ? 'username' : 'email'}.","messagePattern":"Missing (.+?)\\.","errorType":"exception","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/payload/src/auth/operations/unlock.ts","lineNumber":55,"sourceCode":"  } = args\n\n  const loginWithUsername = collectionConfig.auth.loginWithUsername\n\n  const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(loginWithUsername)\n\n  const sanitizedEmail = canLoginWithEmail && (args.data?.email || '').toLowerCase().trim()\n  const sanitizedUsername =\n    (canLoginWithUsername &&\n      'username' in args.data &&\n      typeof args.data.username === 'string' &&\n      args.data.username.toLowerCase().trim()) ||\n    null\n\n  if (collectionConfig.auth.disableLocalStrategy) {\n    throw new Forbidden(req.t)\n  }\n  if (!sanitizedEmail && !sanitizedUsername) {\n    throw new APIError(\n      `Missing ${collectionConfig.auth.loginWithUsername ? 'username' : 'email'}.`,\n      httpStatus.BAD_REQUEST,\n    )\n  }\n\n  try {\n    args = await buildBeforeOperation({\n      args,\n      collection: args.collection.config,\n      operation: 'unlock',\n      overrideAccess,\n    })\n\n    const shouldCommit = await initTransaction(req)\n    let whereConstraint: Where = {}\n\n    // /////////////////////////////////////\n    // Access","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/auth/operations/unlock.ts#L37-L73","documentation":"Thrown in `unlock` when both `sanitizedEmail` and `sanitizedUsername` are falsy after normalization. The operation needs an identifier to find the account to unlock. Message is dynamic: `'Missing username.'` when `loginWithUsername` is enabled, else `'Missing email.'`. `APIError` with HTTP 400 (BAD_REQUEST).","triggerScenarios":"A `POST /api/<collection>/unlock` request body omits both `email` and `username`; `loginWithUsername` is on with `allowEmailLogin` but the client sent neither; the field is present but whitespace-only (trimmed to empty); `username` present but `canLoginWithUsername` is false so it is ignored.","commonSituations":"Form posts an empty identifier field; `loginWithUsername` config added but the frontend still only sends one of the allowed identifiers; the identifier key is mistyped (`user` instead of `username`).","solutions":["Send the identifier your collection expects: `email` for default, or `username`/`email` when `loginWithUsername` is enabled.","Validate non-empty (after trim) on the client before posting.","Match the request shape to `auth.loginWithUsername` settings (`allowEmailLogin`, `canLoginWithUsername`)."],"exampleFix":"// before\nawait payload.unlock({ collection, data: { email: '  ' }, req })\n// after\nconst email = rawEmail.trim()\nif (email) {\n  await payload.unlock({ collection, data: { email }, req })\n}","handlingStrategy":"validation","validationCode":"// Ensure an identifier is present and trimmed\nconst email = data.email?.trim()\nconst username = data.username?.trim()\nif (!email && !username) {\n  throw new Error('Email or username is required')\n}\nawait payload.unlock({ collection, data: { ...(email ? { email } : { username }) }, req })","typeGuard":"function hasUnlockIdentifier(data: unknown): boolean {\n  if (typeof data !== 'object' || !data) return false\n  const d = data as Record<string, unknown>\n  return typeof d.email === 'string' && d.email.trim() !== ''\n    || typeof d.username === 'string' && (d.username as string).trim() !== ''\n}","tryCatchPattern":"try {\n  await payload.unlock({ collection, data, req })\n} catch (e) {\n  if (e instanceof APIError && e.status === 400 && /Missing/.test(e.message)) {\n    // prompt user for email/username\n  } else throw e\n}","preventionTips":["Validate the identifier is non-empty (post-trim) before posting.","Send the identifier matching `auth.loginWithUsername` settings.","Reject whitespace-only submissions client-side."],"tags":["auth","unlock","validation","bad-request"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}