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

  1. Use a different username.
  2. Add a pre-flight `payload.find({ where: { username: { equals } } })` check before register (note: still race-prone).
  3. 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

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


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