Budibase/budibase · error

Cannot set role of account holder

Error message

Cannot set role of account holder

What it means

The worker's save user endpoint protects the account holder: if the user being saved is the registered account owner (found via users.getExistingAccounts by email) and the request does not grant full global admin+builder roles, it throws 'Cannot set role of account holder'. This prevents downgrading or stripping the roles of the account that owns the tenant/subscription.

Source

Thrown at packages/worker/src/api/controllers/global/users.ts:93

  }))
}

export const save = async (ctx: UserCtx<UnsavedUser, SaveUserResponse>) => {
  try {
    const currentUserId = ctx.user?._id
    const tenantId = context.getTenantId()
    const requestUser: User = { ...ctx.request.body, tenantId }

    // Do not allow the account holder role to be changed
    if (
      requestUser.admin?.global !== true ||
      requestUser.builder?.global !== true
    ) {
      const accountMetadata = await users.getExistingAccounts([
        requestUser.email,
      ])
      if (accountMetadata?.length > 0) {
        throw Error("Cannot set role of account holder")
      }
    }

    const user = await userSdk.db.save(requestUser, { currentUserId })

    ctx.body = {
      _id: user._id!,
      _rev: user._rev!,
      email: user.email,
    }
  } catch (err: any) {
    ctx.throw(err.status || 400, err?.message || err)
  }
}

export const changeTenantOwnerEmail = async (
  ctx: Ctx<ChangeTenantOwnerEmailRequest, void>
) => {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Leave the account holder with both admin.global and builder.global set to true in the update payload.
  2. Create a separate admin user first and manage roles through that account instead of modifying the owner.
  3. If ownership must change, use the account/tenant ownership transfer flow (e.g. changeTenantOwnerEmail) rather than editing roles directly.
  4. Filter the account holder's email out of bulk role-sync operations before submitting.

Example fix

// before: stripping owner roles
await saveUser({ email: ownerEmail, admin: { global: false }, builder: { global: false } })
// after: keep owner flags intact
await saveUser({ email: ownerEmail, admin: { global: true }, builder: { global: true } })
Defensive patterns

Strategy: try-catch

Validate before calling

// skip account holders before submitting role updates
const accountHolders = await getAccountHolderEmails()
const updatable = users.filter(u => !accountHolders.includes(u.email) ||
  (u.admin?.global === true && u.builder?.global === true))

Type guard

const isAccountHolderDowngrade = (u: UnsavedUser, holders: string[]): boolean =>
  holders.includes(u.email) &&
  (u.admin?.global !== true || u.builder?.global !== true)

Try / catch

try {
  await api.saveUser(payload)
} catch (e) {
  if (String(e?.message).includes("Cannot set role of account holder")) {
    notify("The account holder's roles cannot be reduced")
  }
}

Prevention

When it happens

Trigger: PUT/POST to the global user save endpoint where requestUser.email matches an existing account holder AND (requestUser.admin?.global !== true || requestUser.builder?.global !== true) — i.e. any attempt to remove admin or builder global flags from the account holder.

Common situations: An admin trying to demote the account owner to a normal user or developer; a bulk role-update script hitting the owner account; syncing users from SSO/SCIM where the owner's roles would be overwritten with reduced scopes.

Related errors


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