Budibase/budibase · error · HTTPError

Account holder cannot be deleted

Error message

Account holder cannot be deleted

What it means

destroy() throws HTTPError(400) 'Account holder cannot be deleted' when an admin (someone other than the account holder) attempts to delete the tenant's root account-holder user from within the product in a cloud deployment. The account holder record is owned by the account portal and cannot be removed in-app.

Source

Thrown at packages/backend-core/src/users/db.ts:577

    })

    return response
  }

  static async destroy(id: string) {
    const db = getGlobalDB()
    const dbUser = (await db.get(id)) as User
    const userId = dbUser._id as string

    if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
      // root account holder can't be deleted from inside budibase
      const email = dbUser.email
      const account = await accountSdk.getAccount(email)
      if (account) {
        if (dbUser.userId === getIdentity()!._id) {
          throw new HTTPError('Please visit "Account" to delete this user', 400)
        } else {
          throw new HTTPError("Account holder cannot be deleted", 400)
        }
      }
    }

    await platform.users.removeUser(dbUser)

    await db.remove(userId, dbUser._rev!)

    const creatorsToDelete = (await isCreatorAsync(dbUser)) ? 1 : 0
    await UserDB.quotas.removeUsers(1, creatorsToDelete)
    await eventHelpers.handleDeleteEvents(dbUser)
    await cache.user.invalidateUser(userId)
    await sessions.invalidateSessions(userId, { reason: "deletion" })
  }

  static async createAdminUser(
    email: string,
    tenantId: string,

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Remove the account (and its holder) through the Account portal, which handles subscription/tenant teardown
  2. Keep the account-holder user and delete other users only
  3. In self-hosted setups this path is not reachable; ensure SELF_HOSTED/DISABLE_ACCOUNT_PORTAL env flags reflect the real deployment

Example fix

// before
await users.destroy(holderId) // holder, deleted by admin
// after
// skip account holders in bulk cleanup
if (!(await accountSdk.getAccount(user.email))) {
  await users.destroy(user._id!)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// skip account holders in bulk deletion
const acct = await accountSdk.getAccount(user.email)
if (acct && user._id !== getIdentity()?._id) {
  // this is the account holder being deleted by an admin - not allowed in-product
}

Try / catch

try {
  await users.destroy(userId)
} catch (e: any) {
  if (e?.status === 400 && e?.message === "Account holder cannot be deleted") {
    // remove via Account portal or skip this user in bulk jobs
  } else throw e
}

Prevention

When it happens

Trigger: destroy() on a user whose email has an account in the account portal, where dbUser.userId differs from the current identity (i.e., an admin deleting the holder), with env.SELF_HOSTED=false and DISABLE_ACCOUNT_PORTAL=false.

Common situations: Admins offboarding the original signup user; bulk delete scripts that include the account holder; trying to transfer ownership by deleting and recreating the holder.

Related errors


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