Account Deletion Policies
Customize account deletion authorization rules.
Account deletion policies control when users can delete their accounts. The system ships with default policies that can be extended or replaced.
Default Policies
The @kit/account-hooks package includes two default policies, defined under packages/account/hooks/src/policies/:
| Policy | ID | Behavior |
|---|---|---|
checkSubscriptionsPolicy | account-deletion.check-subscriptions | Blocks if user has active subscriptions |
checkOrganizationsPolicy | account-deletion.check-organizations | Blocks if sole owner of orgs with other members |
The package root (
@kit/account-hooks) only re-exports the hook functionsbeforeAccountDelete,afterAccountDelete,cascadeDeleteOwnedOrganizations, andcheckAccountDeletionEligibility. The policy symbols below (AccountDeleteContext,accountDeletionRegistry,accountDeletion,checkSubscriptionsPolicy,checkOrganizationsPolicy) live in the package's internal./policiesmodule and are not exported from the root. To customize, edit inside the package (e.g.src/policies/registry.ts) or add a subpath export.
Adding Custom Policies
1. Create Your Policy
// Inside packages/account/hooks/src/policies/import { definePolicy, allow, deny } from '@kit/policies';import type { AccountDeleteContext } from './types';export const gracePeriodPolicy = definePolicy<AccountDeleteContext>({ id: 'account-deletion.grace-period', evaluate: async (context) => { const user = await getUser(context.userId); const daysSinceCreation = daysBetween(user.createdAt, new Date()); if (daysSinceCreation < 7) { return deny({ code: 'ACCOUNT_TOO_NEW', message: 'Accounts must be at least 7 days old before deletion', remediation: `Please wait ${7 - daysSinceCreation} more days`, }); } return allow(); },});2. Register Your Policy
Extend the default registry:
// In packages/account/hooks/src/policies/registry.tsimport { accountDeletionRegistry } from './registry';import { gracePeriodPolicy } from './grace-period.policy';// Add to existing defaultsaccountDeletionRegistry.registerPolicy(gracePeriodPolicy);Context Type
interface AccountDeleteContext { userId: string; // User attempting to delete account timestamp: string; // ISO timestamp}Customization Patterns
Use Defaults As-Is
// From within the package (e.g. before-delete.ts)import { accountDeletion } from './policies';const result = await accountDeletion.run({ userId: user.id, timestamp: new Date().toISOString(),});Extend Defaults
// From within the packageimport { accountDeletionRegistry } from './policies';import { myCustomPolicy } from './my-custom.policy';accountDeletionRegistry.registerPolicy(myCustomPolicy);Cherry-Pick Policies
import { createPolicyRegistry, createPolicyRuntime } from '@kit/policies';import { checkOrganizationsPolicy } from './policies';const registry = createPolicyRegistry();// Only check organizations, skip subscriptionsregistry.registerPolicy(checkOrganizationsPolicy);const runtime = createPolicyRuntime(registry);Replace a Policy
import { createPolicyRegistry, createPolicyRuntime } from '@kit/policies';import { checkOrganizationsPolicy } from './policies';import { mySubscriptionPolicy } from './my-subscription.policy';const registry = createPolicyRegistry();registry.registerPolicy(checkOrganizationsPolicy);registry.registerPolicy(mySubscriptionPolicy); // Your replacementconst runtime = createPolicyRuntime(registry);Example: Auto-Cancel Subscriptions
Instead of blocking deletion, auto-cancel subscriptions:
import { definePolicy, allow } from '@kit/policies';export const autoCancelPolicy = definePolicy<AccountDeleteContext>({ id: 'account-deletion.auto-cancel-subscriptions', evaluate: async (context) => { const subscriptions = await getActiveSubscriptions(context.userId); for (const sub of subscriptions) { await cancelSubscription(sub.id); } return allow({ cancelledSubscriptions: subscriptions.length, }); },});Example: Require Data Export
export const dataExportPolicy = definePolicy<AccountDeleteContext>({ id: 'account-deletion.require-export', evaluate: async (context) => { const hasExported = await checkDataExportStatus(context.userId); if (!hasExported) { return deny({ code: 'DATA_NOT_EXPORTED', message: 'Please export your data before deleting your account', remediation: 'Go to Settings > Privacy > Export Data', }); } return allow(); },});Integration
The policies are evaluated in the Better Auth beforeDeleteUser hook:
// packages/account/hooks/src/before-delete.tsimport { accountDeletion } from './policies';export async function beforeAccountDelete(userId: string, userEmail?: string) { const result = await accountDeletion.run({ userId, timestamp: new Date().toISOString(), }); if (!result.allowed) { throw new Error(result.reasons.join(' ')); }}Next: Preferences →