payloadcms/payload · error · ValidationError
A user with the given username is already registered.
Error message
A user with the given username is already registered.
What it means
In `registerLocalStrategy`, after building a where-constraint by username (or username+email) it queries for existing users; on any match it throws a `ValidationError` carrying the localized `usernameAlreadyRegistered` message on the `username` path. Fires only when `canLoginWithUsername` is true (collection supports username login).
Source
Thrown at packages/payload/src/auth/strategies/local/register.ts:68
whereConstraint.or?.push({
username: {
equals: doc.username,
},
})
}
}
const existingUser = await payload.find({
collection: collection.slug,
depth: 0,
limit: 1,
pagination: false,
req,
where: whereConstraint,
})
if (existingUser.docs.length > 0) {
throw new ValidationError({
collection: collection.slug,
errors: [
canLoginWithUsername
? {
message: req.t('error:usernameAlreadyRegistered'),
path: 'username',
}
: { message: req.t('error:userEmailAlreadyRegistered'), path: 'email' },
],
})
}
const { hash, salt } = await generatePasswordSaltHash({ collection, password, req })
const sanitizedDoc = { ...doc }
if (sanitizedDoc.password) {
delete sanitizedDoc.password
}View on GitHub (pinned to 00c58b35c0)
Solutions
- Use a different username.
- Add a pre-flight `payload.find({ where: { username: { equals } } })` check before register (note: still race-prone).
- Enforce a unique index at the DB layer to eliminate the race.
Example fix
// before
await payload.create({ collection: 'users', data: { username: 'taken', password, ... } })
// after
const taken = await payload.find({ collection: 'users', limit: 1, pagination: false, where: { username: { equals: 'taken' } } })
if (taken.docs.length) throw new Error('username taken')
await payload.create({ collection: 'users', data: { username: 'taken', password, ... } }) Defensive patterns
Strategy: try-catch
Validate before calling
async function isUsernameAvailable(payload, slug, username) {
const res = await payload.find({ collection: slug, limit: 1, pagination: false, where: { username: { equals: username } } })
return res.docs.length === 0
} Type guard
function isValidationError(e): e is ValidationError {
return e?.name === 'ValidationError' && Array.isArray(e?.data?.errors)
} Try / catch
try {
await payload.create({ collection: 'users', data })
} catch (e) {
if (isValidationError(e) && e.data.errors.some(x => x.path === 'username')) {
setFieldError('username', 'already taken')
} else throw e
} Prevention
- Pre-check username availability (still race-prone without a DB constraint).
- Enforce a unique index at the DB level.
- Map the `ValidationError` to the username field on the form.
When it happens
Trigger: Calling the register/create-first-user operation with a `username` (or email, when `canLoginWithEmail`) that already exists in a username-login-enabled auth collection.
Common situations: Duplicate signup attempt; re-running a seed/import script; only app-layer uniqueness enforcement (DB allows the race); username collision from a soft-deleted then restored user.
Related errors
- A user with the given email is already registered.
- Username or email is required
- validation:required
- Username is required.
- error:notAllowedToPerformAction
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/a99de87e01a32a9f.
Report an issue: GitHub.