Budibase/budibase · error

User ID missing

Error message

User ID missing

What it means

assignExistingUsersToWorkspace adds existing users to workspace roles/groups. Before calling groupStore.addUser(groupId, user._id) it asserts user._id is present. A user entry lacking an _id cannot be referenced by group APIs, so it throws 'User ID missing'. It guards against malformed user objects returned by the bulk-save flow.

Source

Thrown at packages/builder/src/settings/pages/people/users/workspaceInviteUtils.ts:269

            shouldSyncGlobalRole(selectedRole, user))
        ) {
          const loaded = await users.get(user._id)
          if (loaded) {
            fullUser = loaded
            rev = loaded._rev
          }
        }
        if (
          user._id &&
          fullUser &&
          shouldSyncGlobalRole(selectedRole, fullUser)
        ) {
          const roleUpdates = getRoleFlags(selectedRole, fullUser)
          const saved = await users.save({ ...fullUser, ...roleUpdates })
          rev = saved?._rev || rev
        }
        if (!user._id) {
          throw new Error("User ID missing")
        }
        if (groupIdsToAdd.length) {
          await Promise.all(
            groupIdsToAdd.map(groupId => groupStore.addUser(groupId, user._id!))
          )
        }
        if (!role) {
          return email
        }
        if (groupIdsToAdd.length || !rev) {
          const loaded = await users.get(user._id)
          rev = loaded?._rev
        }
        if (!user._id || !rev) {
          throw new Error("User ID or revision missing")
        }
        await users.addUserToWorkspace(user._id, role, rev)
        return email

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Ensure each user passed to assignExistingUsersToWorkspace is a fully fetched existing user with _id (use users.get by email/id first).
  2. Filter out users without _id before assignment and route them through the create/invite flow instead.
  3. Refresh the user list before assignment to remove stale/partial records.

Example fix

// before
users.forEach(u => assign(u))
// after
const assignable = users.filter(u => !!u._id)
assignable.forEach(u => assign(u))
Defensive patterns

Strategy: validation

Validate before calling

const missing = selectedUsers.filter(u => !u._id)
if (missing.length) {
  // resolve via users.get by email or route to invite flow
  await Promise.all(missing.map(u => resolveOrCreateUser(u.email)))
}

Type guard

const isExistingUser = (u: Partial<User>): u is User & { _id: string } =>
  typeof u._id === "string" && u._id.length > 0

Try / catch

try {
  await assignExistingUsersToWorkspace({ users, ... })
} catch (e) {
  if (e.message === "User ID missing") {
    // fall back to create/invite flow for entries without ids
    return inviteNewUsers(users.filter(u => !u._id))
  }
  throw e
}

Prevention

When it happens

Trigger: Processing a selected user in assignExistingUsersToWorkspace whose object has no _id — e.g. the user record was constructed client-side from an email-only entry, or a partial object from a search/list response was passed instead of a full fetched user.

Common situations: Inviting via pasted emails where the email does not match an existing user so no _id was resolved; stale cached user lists; mixing 'existing user' and 'new invite' paths so an invite-only entry reaches the existing-user branch.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/3ad0b568a358be52. Report an issue: GitHub.