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/:

PolicyIDBehavior
checkSubscriptionsPolicyaccount-deletion.check-subscriptionsBlocks if user has active subscriptions
checkOrganizationsPolicyaccount-deletion.check-organizationsBlocks if sole owner of orgs with other members

The package root (@kit/account-hooks) only re-exports the hook functions beforeAccountDelete, afterAccountDelete, cascadeDeleteOwnedOrganizations, and checkAccountDeletionEligibility. The policy symbols below (AccountDeleteContext, accountDeletionRegistry, accountDeletion, checkSubscriptionsPolicy, checkOrganizationsPolicy) live in the package's internal ./policies module 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.ts
import { accountDeletionRegistry } from './registry';
import { gracePeriodPolicy } from './grace-period.policy';
// Add to existing defaults
accountDeletionRegistry.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 package
import { 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 subscriptions
registry.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 replacement
const 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.ts
import { 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 →