#10748·better-auth

feat: email confirmation for org deletion & ownership transfer, plus opt-in confirmationMode

Author: mcorbelliCreated Aug 10, 2026Updated Sep 17, 2026
Labelsorganization

Is this suited for github?

  • Yes, this is suited for github

Is your feature request related to a problem? Please describe.

The user.deleteUser flow supports an optional email confirmation step (sendDeleteAccountVerification): the user requests deletion, gets an email with a confirmation link, and the account is only deleted after they click it. This protects against accidental or malicious one-click account deletion (e.g. XSRF-ish misuse, a compromised session, a stray API call).

The organization plugin has no equivalent for its two most destructive member-facing actions:

  • organization/delete deletes the organization immediately once the caller passes the organization:delete permission check — no confirmation step.
  • There is no "transfer ownership" operation at all. Reassigning the creatorRole (owner) today means calling organization/update-member-role directly, which takes effect immediately with no verification that the current owner actually intended to give up control.

Both are effectively irreversible from the acting member's perspective (data loss for deletion, loss of control for ownership transfer), so they deserve the same opt-in confirmation pattern deleteUser already has.

Describe the solution you'd like

Mirror the existing user.deleteUser contract for these two organization actions, as two independent, opt-in features under OrganizationOptions:

1. Organization deletion confirmation

typescript
organization({
  organizationDeletion: {
    sendDeleteOrganizationVerification: async ({ organization, user, url, token }, request) => {
      // send email
    },
    deleteTokenExpiresIn: 60 * 60 * 24, // default 1 day
  },
})
  • organization/delete gains an optional token field in its body, same as deleteUser. Without sendDeleteOrganizationVerification configured, behavior is unchanged (immediate deletion) — fully backward compatible.
  • With it configured, the first call creates a single-use verification token scoped to (organizationId, requesting user), emails a callback URL, and returns { success: true, message: "Verification email sent" } instead of deleting.
  • New GET /organization/delete/callback?token=... endpoint (mirroring /delete-user/callback) re-validates the session and permission, consumes the token atomically, then runs the existing deletion path (including beforeDeleteOrganization/afterDeleteOrganization hooks).

2. Ownership transfer confirmation

A new dedicated endpoint rather than overloading update-member-role (which is a generic, synchronous, multi-role endpoint and shouldn't grow an async confirmation branch):

typescript
organization({
  ownershipTransfer: {
    sendTransferOwnershipVerification: async ({ organization, currentOwner, newOwner, url, token }, request) => {
      // send email
    },
    transferTokenExpiresIn: 60 * 60 * 24,
  },
})
  • POST /organization/transfer-ownership — body: organizationId, newOwnerMemberId. Callable only by a current owner (or someone with organization:update on the owner role, consistent with today's last-owner rules in update-member-role).
  • Without sendTransferOwnershipVerification configured: performs the same atomic demote-current/promote-target role swap that calling update-member-role with the creator role does today.
  • With it configured: emails the current owner (not the target — they didn't initiate anything) a confirmation link; only on click does the atomic role swap happen. Token is single-use, scoped to (organizationId, current owner, target member) so it can't be replayed against a different target after org membership changes.
  • Reuses the existing "can't leave an org with zero owners" guard from update-member-role/remove-member/leave-organization.

Both features:

  • Are fully opt-in (no config change = current behavior, no breaking change).
  • Get beforeDelete/afterDelete-style hooks where useful, consistent with existing organizationHooks.
  • Ship with unit tests under packages/better-auth/src/plugins/organization/routes/*.test.ts and docs updates in docs/content/docs/plugins/organization.mdx.

Describe alternatives you've considered

  • Overloading update-member-role with a confirmation branch for the creator role — rejected: that endpoint handles arbitrary multi-role updates for any member, not just ownership; bolting an async email-confirmation branch onto it changes its contract in a confusing, role-specific way and complicates testing/typing for a case that's a small minority of its calls.
  • A single generic "confirm sensitive org action" token type reused across delete/transfer — rejected for the initial version: the two actions have different payloads (org vs. two members) and different recipients (acting member vs. current owner); a shared abstraction can be extracted later if a third use case shows up, but building it now would be speculative.

Additional context

Existing prior art in this codebase to follow for both the API shape and the security details (atomic single-use token consumption via consumeVerificationValue, session-freshness bypass on the callback via disableCookieCache, originCheck middleware on the callback URL):

  • packages/better-auth/src/api/routes/update-user.ts (deleteUser, deleteUserCallback)
  • Options contract: packages/core/src/types/init-options.ts (user.deleteUser)

Related existing organization primitives this feature builds on:

  • packages/better-auth/src/plugins/organization/routes/crud-org.ts (deleteOrganization, beforeDeleteOrganization/afterDeleteOrganization hooks)
  • packages/better-auth/src/plugins/organization/routes/crud-members.ts (updateMemberRole, existing last-owner protection logic)