{"record":{"id":"ec3d4e554a066e00","repo":"Budibase/budibase","slug":"email-already-in-use-email","errorCode":null,"errorMessage":"Email already in use: '${email}'","messagePattern":"Email already in use: '(.+?)'","errorType":"exception","errorClass":"EmailUnavailableError","httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/users/db.ts","lineNumber":273,"sourceCode":"      try {\n        dbUser = await usersCore.getById(_id)\n        if (email && dbUser.email !== email && !opts.allowChangingEmail) {\n          throw new Error(\"Email address cannot be changed\")\n        }\n      } catch (e: any) {\n        if (e.status === 404) {\n          // do nothing, save this new user with the id specified - required for SSO auth\n        } else {\n          throw e\n        }\n      }\n    }\n\n    if (!dbUser && email) {\n      // no id was specified - load from email instead\n      dbUser = await usersCore.getGlobalUserByEmail(email)\n      if (dbUser && dbUser._id !== _id) {\n        throw new EmailUnavailableError(email)\n      }\n    }\n\n    const isNewUser = !dbUser\n    const isEmailChanging = !!dbUser && !!email && dbUser.email !== email\n    const shouldValidateUniqueUser =\n      !opts.isAccountHolder && !!email && (isNewUser || isEmailChanging)\n\n    // For new users, resolve effective group assignment before creator quota\n    // calculation so creator-by-group users are counted correctly.\n    let groupIdsToAssign = [...userGroups]\n    if (isNewUser && groupIdsToAssign.length === 0) {\n      const defaultGroup = await UserDB.groups.getDefaultGroup?.()\n      if (defaultGroup?._id) {\n        groupIdsToAssign = [defaultGroup._id]\n      }\n    }\n","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/users/db.ts#L255-L291","documentation":"save() throws EmailUnavailableError (\"Email already in use: '<email>'\") when no user document was found for the given _id but a user with the same email already exists in the global DB with a different _id. It guards the uniqueness of emails per tenant when saving users by id rather than email.","triggerScenarios":"Calling save({ _id }) with an email that belongs to a different existing user; updating a user's email to one already taken by another user (isEmailChanging path leads into validateUniqueUser / this check).","commonSituations":"Client sends a stale or wrong _id with a known email; inviting users whose email already exists; merging accounts; concurrent signups racing to claim the same email.","solutions":["Look up the existing user with getGlobalUserByEmail and operate on that _id instead","Choose a different email if this is genuinely a new user","If the email should be reassigned, delete/update the conflicting user first","Check whether the client cached an outdated _id and refresh the user record"],"exampleFix":"// before\nawait users.save({ _id: wrongId, email: \"a@example.com\" })\n// after\nconst existing = await usersCore.getGlobalUserByEmail(\"a@example.com\")\nawait users.save({ _id: existing!._id, email: \"a@example.com\" })","handlingStrategy":"try-catch","validationCode":"// check availability before saving\nconst existing = await usersCore.getGlobalUserByEmail(email)\nif (existing && existing._id !== targetId) {\n  throw new Error(`Email ${email} is already used by user ${existing._id}`)\n}","typeGuard":null,"tryCatchPattern":"import { EmailUnavailableError } from \"@budibase/backend-core\"\ntry {\n  await users.save({ _id, email })\n} catch (e) {\n  if (e instanceof EmailUnavailableError) {\n    // load the conflicting user or prompt for a different email\n  } else throw e\n}","preventionTips":["Fetch the user by email first when the _id may be stale","Never echo a full user object back into save; send only changed fields","Handle EmailUnavailableError distinctly in API clients with a 400-friendly message"],"tags":["uniqueness","email-conflict","validation"],"backgroundTag":"email-already-in-use","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}