Align server deletion policy, email confirmation, and BTST account controls, then verify sessions and application cleanup.

BTST's delete-account dialog starts an account-removal request. The server must separately enable deletion, verify the caller, and decide what happens to application data. If confirmation by email is required, configure both the server callback and the UI's pending-confirmation state.
This guide targets @btst/better-auth-ui@2.0.1, @btst/stack@3.1.2, and better-auth@1.6.16. It assumes the auth and account pages already work. Use disposable accounts when testing; a completed deletion is a destructive operation.
Before enabling the control, inspect the data related to a user ID: authentication records, organization membership, authored content, uploads, billing references, and external services. Better Auth's account endpoint does not define a retention policy or automatically erase every record your application owns.
Choose explicitly whether authored content is retained, anonymized, transferred, or removed. Resolve sole-owner organizations and active subscriptions according to the product's rules. Do not make a browser dialog responsible for enforcing those rules: another client can call the endpoint directly.
The pinned server deletion implementation invokes beforeDelete before removal and afterDelete afterward. Use these server hooks where appropriate, but do not assume external cleanup and database deletion form one atomic transaction. A failure after removal needs a durable, retryable cleanup process. Verify your actual adapter's relations and constraints before relying on cascades.
Merge this fragment into the existing Better Auth options, preserving other user settings:
import type { BetterAuthOptions } from "better-auth";
import { after } from "next/server";
import { sendEmail } from "@/lib/email";
export const deletionPolicy = {
user: {
deleteUser: {
enabled: true,
sendDeleteAccountVerification: async ({ user, url }) => {
after(async () => {
await sendEmail({
to: user.email,
subject: "Confirm account deletion",
text: `Confirm removal of your account: ${url}`,
});
});
},
},
},
} satisfies BetterAuthOptions;
sendEmail is your existing delivery adapter. after is the Next.js request-lifecycle mechanism here; use your host's equivalent outside Next.js. Delivery monitoring must omit the link and token. In a product where email ownership matters for this decision, require verified addresses through the existing registration and account policies.
With this callback, a successful initial request reports that verification was sent. The user still exists at that point. The generated URL invokes Better Auth's deletion callback when opened; its callback URL controls navigation afterward, not where verification occurs. Preserve the generated URL and the endpoint's origin checks.
In the pinned release, completing that callback requires a session for the same user as the token. Opening the message in an unrelated browser or while signed in as another user does not authorize deletion. Make that requirement clear in your help text and test it across devices. See Better Auth's account documentation for the broader flow.
Merge the following into the existing StackProvider overrides.account value:
import type { AccountPluginOverrides } from "@btst/better-auth-ui/client";
export const deletionUI = {
deleteUser: { verification: true },
} satisfies Partial<AccountPluginOverrides>;
With default paths under /p, the card appears at /p/account/security. The released dialog asks credential users for a password. For an account without a linked password, it sends a stale session through sign-out before retrying. Keep the companion's freshAge aligned with the server policy.
The UI's verification flag changes its confirmation message and callback navigation. It does not make the server send email. If that UI flag is enabled while the server callback is missing, the server may complete deletion immediately under its ordinary password or fresh-session rules while the interface says to check email. Conversely, configuring the server callback while leaving the UI in immediate-deletion mode gives misleading feedback. Test the pair together.
Use two disposable accounts and a separate browser session:
| Action | What to verify |
|---|---|
| Call without a session | The server rejects the request |
| Submit an incorrect password | The account remains and no deletion message is sent |
| Submit a valid request | Confirmation is sent; user, sessions, and application data still exist |
| Open the link without its user's session | Deletion is refused |
| Open it as the other account | Neither account is removed |
| Open an expired token | Deletion is refused |
| Complete with the correct account | Auth records and sessions are removed as expected; application cleanup follows its defined policy |
| Reuse the completed token | It cannot delete another account |
After completion, test protected server access using every previously active test session. Account removal must be reflected in server authorization, not merely in the current tab. Review any application-managed session or permission cache separately.
Do not report all data erased because the auth row disappeared. Keep a separate verification record for the application records and external effects your policy requires. Likewise, successful email scheduling is not confirmed delivery or completed deletion.
The snippets were type-checked and the pinned auth handler was tested with an in-memory adapter and captured email callbacks. These checks cover confirmation and access conditions; real delivery, production database constraints, billing or storage cleanup, and the complete browser flow were not exercised. Continue with session management and organization setup when reviewing the related account lifecycle.