calcom/cal.diy · warning · HttpError

Username or email is already taken

Error message

Username or email is already taken

What it means

Thrown by calcomSignupHandler (HttpError, HTTP 409) when validateAndGetCorrectedUsernameAndEmail reports isValid === false in the non-team-invite signup branch. HTTP 409 signals a conflict: the username or email is already owned by an existing account.

Source

Thrown at apps/web/app/api/auth/signup/handlers/calcomSignupHandler.ts:121

      teamId: foundToken?.teamId ?? null,
      isSignup: true,
    });

    if (foundToken?.teamId) {
      const existingUser = await userRepository.findByEmailWithInvitedTo({email})

      if (existingUser && existingUser.invitedTo !== foundToken.teamId) {
        return NextResponse.json({ message: SIGNUP_ERROR_CODES.USER_ALREADY_EXISTS }, { status: 409 });
      }
    }
  } else {
    const usernameAndEmailValidation = await validateAndGetCorrectedUsernameAndEmail({
      username,
      email,
      isSignup: true,
    });
    if (!usernameAndEmailValidation.isValid) {
      throw new HttpError({
        statusCode: 409,
        message: "Username or email is already taken",
      });
    }

    if (!usernameAndEmailValidation.username) {
      throw new HttpError({
        statusCode: 422,
        message: "Invalid username",
      });
    }

    username = usernameAndEmailValidation.username;
  }

  // Create the customer in Stripe with ad tracking metadata
  const cookieStore = await cookies();
  const cookiesObj = Object.fromEntries(cookieStore.getAll().map((c) => [c.name, c.value]));

View on GitHub (pinned to 176037d0af)

Solutions

  1. Pre-check username/email availability (debounced) before submit and suggest alternatives.
  2. Normalize email to lowercase and trim before checking, matching the handler's userEmail.toLowerCase().
  3. On 409, prompt login/password-reset instead of re-registration.
  4. If the user was invited to a team, include the invite token so the team branch resolves ownership rather than conflicting.

Example fix

// before
await signup({ username, email });

// after
const avail = await checkUsernameEmail({ username, email });
if (!avail.isValid) {
  setErrors({ username: 'taken', email: 'taken' });
  return;
}
await signup({ username: avail.username ?? username, email: email.toLowerCase() });
Defensive patterns

Strategy: validation

Validate before calling

// Debounced availability check before signup
const ok = await checkAvailability({ username, email: email.toLowerCase() });
if (!ok.available) {
  setErrors({ username: ok.usernameTaken ? 'Username taken' : undefined,
              email: ok.emailTaken ? 'Email taken' : undefined });
  return;
}
await signup({ username, email: email.toLowerCase() });

Type guard

function isAvailabilityResult(v: unknown): v is { isValid: boolean; username?: string } {
  return !!v && typeof v === 'object' &&
    typeof (v as any).isValid === 'boolean';
}

Try / catch

try {
  await signup(payload);
} catch (e) {
  if (e instanceof HttpError && e.statusCode === 409) {
    promptLoginOrReset(); // offer login/reset instead of re-register
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Signup POST with a username or email that already exists in the database, when no valid team-invite token is present (the token branch handles team members separately and returns a 409 JSON instead).

Common situations: User re-registering with the same email, choosing a taken username, leftover account from a failed prior signup, case/whitespace differences not normalized before the check.

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/933c989876592938. Report an issue: GitHub.