When to use it
A new table, bucket or external service holds data of a user, for example journal entries, uploaded documents or a newsletter subscription. "Delete account" in the privacy controls must remove it too.
Rules to keep
- Account deletion removes everything the product stores for the user and then
the Auth user, as
PRODUCT.mdpromises. - Only the
delete-accountEdge Function deletes, for the user of the JWT it has verified.public.delete_my_accountstays executable only byservice_role; never take the user id from the request body. - Files are removed through the Storage API. A
delete from storage.objectsonly removes the metadata and leaves the file in the bucket. - Every step that fails stops with an error before the Auth user is deleted, so the user can retry.
- Migrations are append-only: redefine the function in a new migration.
Steps
Give every new table a
user_id uuid not null references auth.users(id) on delete cascade(or a reference topublic.profiles(id)), so its rows can never outlive the account.Redefine
public.delete_my_accountin a new migration withcreate or replace function. Copy the body from the latest migration that defines it (now202611020001_fix_account_deletion.sql) and add the new table, so the function remains the complete list of what deletion removes:delete from public.journal_entries where user_id = p_user_id;Keep the signature and
security definer set search_path = '', and repeat therevoke execute ... from public, anon, authenticatedandgrant execute ... to service_rolelines.Store files under
<user-id>/..., as theavatarsbucket does. Insupabase/functions/delete-account/index.tsremove them like the avatars, before the RPC:list(user.id)returns at most 100 entries and does not enter subfolders, so repeatlistandremoveuntil the list is empty, or keep the folder flat.For data in an external service, call its deletion API from the Edge Function with a key kept in the function secrets, before the RPC. If it fails, answer 500 as the other steps do.
Extend
supabase/tests/database/delete_my_account.test.sql: as the test owner insert rows for two users, callpublic.delete_my_accountfor the first one and check that only their rows are gone. Keep the privilege checks that are already there.Run
supabase db resetandsupabase test db, then delete a test account from the app and check in the dashboard that no rows or files remain.Describe the new data in
DATABASE.mdand its retention inPRODUCT.md.
Done when
- the verification commands and
supabase test dbpass; - deleting a test account leaves no rows, files or external records;
delete_my_accountis still not executable byanonorauthenticated;DATABASE.mdandPRODUCT.mddescribe the new data.
