An email arrives: “Under data protection law, please send me all the personal data you hold about me,” or “Please delete my data.” It might come from a former customer, a newsletter subscriber or someone who filled in your contact form once. You have a deadline, you want to do it properly, and you are not sure where your WordPress site even keeps their details.
WordPress has built-in tools for exactly this, under Tools, Export Personal Data and Erase Personal Data. They are good, but they only cover WordPress itself and the plugins that choose to plug into them. This guide shows you how to run a request end to end, find the data the tools miss, and keep a record. It is a practical guide, not legal advice: deadlines, exemptions and what you may keep vary by law, so check anything uncertain with a qualified adviser.
What is happening
WordPress treats every privacy request as a small workflow stored in the database. Each request is saved as a post of type user_request, with the requester’s email address in the title and the request type in the slug: export_personal_data or remove_personal_data. Its status moves through these stages.
| Status |
Shown as |
Meaning |
request-pending |
Pending |
Created; waiting for the person to click the link in the confirmation email. |
request-confirmed |
Confirmed |
The person clicked the link, proving they control the address. The site admin gets a notification email. |
request-failed |
Failed |
The confirmation link expired before it was used. By default links last 24 hours. |
request-completed |
Completed |
You (or WordPress) marked it done after exporting or erasing. |
The actual searching is done by exporters and erasers: small functions registered by WordPress core and by plugins. When you run an export, WordPress calls every registered exporter with the email address, collects what they return and builds a zip file. When you run an erasure, it calls every eraser.
| Source |
Exporter |
Eraser |
| WordPress core: user profile (also session tokens and Community Events location) |
Yes |
No: the user account is not deleted |
| WordPress core: comments |
Yes |
Yes: comments are kept but author name, email, URL and user agent are cleared and the IP is anonymized |
| WordPress core: media uploaded by the user |
Yes (list of file URLs) |
No |
| WooCommerce: customer data, orders, downloads, payment tokens |
Yes |
Yes, subject to its settings |
| Other plugins |
Only if the plugin registers one |
Only if the plugin registers one |
Anything outside WordPress, such as your inbox, mailing list service, CRM, accounting software, backups and server logs, is never touched by these tools.
Before you start
You need an administrator account (on multisite, a network admin). Take a full backup before any erasure, because erasing cannot be undone through WordPress. Do not test erasure on a real customer: test on your own address first.
Most steps use admin screens. Optional WP-CLI commands run over SSH in the site folder, the one that contains wp-config.php; your host may call the command wp or wp-cli. Make sure WordPress can send email (see the separate guide on WordPress not sending email if confirmation messages never arrive).
Note the date the request reached you, in writing. Under GDPR and UK GDPR the usual deadline is one month from receipt, extendable in some circumstances; other laws set their own timelines.
Step 1: Log the request and find the person’s data
Before touching any tool, write down who asked, when, what they asked for (a copy, deletion or both) and how they contacted you. Then find out where this person exists on your site.
Check for a user account
wp user get EMAIL_ADDRESS --fields=ID,user_login,user_registered,roles
Run this over SSH. Replace EMAIL_ADDRESS with the address from the request. wp user get accepts an ID, username or email; --fields limits the output. Good: a table with the user’s ID and role, telling you a WordPress account exists. An error saying the user is invalid means there is no account, which is normal for contact form senders and guest buyers.
Search every table for the address
wp db search 'EMAIL_ADDRESS' --all-tables-with-prefix --stats
Run this over SSH; it only reads. Replace EMAIL_ADDRESS with the address, keeping the quotes. --all-tables-with-prefix includes plugin tables that use your WordPress prefix; --stats adds a count of matches per table. Good output lists each table and column where the address appears, such as wp_comments:comment_author_email, a form plugin’s entries table, a newsletter plugin’s subscribers table or an email log table. This is your map of where the data lives. If a table appears that belongs to a plugin without an exporter, you will need to handle it by hand in Step 5. On a very large database, add a table name after the search term to limit it.
Step 2: Create the request in WordPress
Go to Tools, then Export Personal Data (for a copy) or Erase Personal Data (for deletion). Under “Add Data Export Request” or “Add Data Erasure Request”, enter the username or email address.
The confirmation email checkbox
Leave “Send personal data export confirmation email” (or the erasure equivalent) checked in most cases. WordPress emails the person a link; clicking it proves they control that address before you hand over or destroy anything. That protects against someone requesting another person’s data.
Uncheck it only if you have already verified identity another way, for example the request came from the same address while the person was logged into their account. Without the email, the request is created already confirmed.
If the link expires
Confirmation links are valid for 24 hours by default. If the person misses the window, the request shows as Failed. Check it in the list, choose “Resend confirmation requests” from Bulk actions, and apply. Tell the person to look out for the email and check spam.
When they confirm, WordPress emails the site admin and the status changes to Confirmed.
Step 3: Run the export
Once a request is Confirmed, hover over it in the Export Personal Data list. You can choose “Download personal data” to get the zip yourself, or “Send export link” to email the person a download link. The “Next steps” column offers the same options.
What is in the file
The zip contains index.html, a readable report grouped by source (User, Comments, Media, WooCommerce Orders and so on), and export.json with the same data in machine-readable form. Download it yourself first, even if you plan to send the link, and read it. Check that it contains only this person’s data and that nothing is obviously missing based on your Step 1 search.
The zip is stored in wp-content/uploads/wp-personal-data-exports/ under a randomized file name, and WordPress deletes export files older than three days on an hourly scheduled task. The emailed link will stop working after that.
Common export errors
- “Unable to generate personal data export file. ZipArchive not available.”: the PHP zip extension is missing. Ask your host to enable it.
- “Unable to create personal data export folder.”: WordPress cannot write to
wp-content/uploads. Check folder permissions and free disk space.
- The progress spinner never finishes: one exporter is failing. Open the browser DevTools Network tab, retry, and look at the failing
admin-ajax.php response; it usually names the exporter or shows a PHP error. Check the PHP error log at the same time.
Add anything you found outside WordPress (Step 5) as a separate file or message, then mark the request complete: hover and click “Complete request”, or use the bulk action “Mark requests as completed”.
Step 4: Run the erasure
For a Confirmed erasure request, hover and choose “Erase personal data”. For a request still Pending, WordPress also offers “Force erase personal data”; use that only when you have verified identity some other way.
Read the result messages
Each eraser reports whether items were removed or retained, and may add messages explaining why something was kept. For example, WooCommerce may retain order data it is set to keep. Read every message and copy them into your record. When the request completes, WordPress emails the person that their erasure request has been fulfilled.
What erasure does not do
- It does not delete the WordPress user account. If the person wants their account removed, delete it under Users. Choose whether to delete their posts or attribute them to another user.
- It does not delete comments. It anonymizes them: the text stays, the author shows as “Anonymous”. If the comment text itself contains personal information, edit or delete it by hand.
- It does not remove media files the user uploaded. Review them and delete any that identify the person.
To delete an account from the command line instead:
wp user delete USER_ID --reassign=NEW_AUTHOR_ID
Run this over SSH. USER_ID is the ID from Step 1. NEW_AUTHOR_ID is the ID of the account that should inherit their posts, found with wp user list. Without --reassign, the user’s posts are deleted with the account, so think before omitting it. Good output: “Success: Removed user”. If the account owns content you need, always reassign.
Step 5: Handle what the tools miss
This is where most incomplete responses come from.
Form plugin entries
If your Step 1 search found the address in a form entries table, check whether the form plugin supports the WordPress privacy tools. Gravity Forms, for example, has a per-form Personal Data setting that connects the form to the exporter and eraser and can set a retention policy. Other plugins may offer their own export or delete options in their entries screen, or none at all. Test with your own address: if the plugin’s data did not appear in your test export, it does not participate, and you must export or delete those entries in the plugin’s own screen.
WooCommerce
Go to WooCommerce, then Settings, then Accounts & Privacy. Under “Account erasure requests”, “Remove personal data from orders on request” decides whether an erasure request anonymizes order data; “Remove access to downloads on request” handles downloadable products. Under “Personal data removal”, “Allow personal data to be removed in bulk from orders” adds a bulk action in the orders list. The “Personal data retention” section sets how long inactive accounts and pending, failed, canceled, refunded and completed orders are kept before they are trashed or anonymized. Tax and accounting rules often require keeping order records for years, so agree those settings with your accountant or adviser before turning on removal from orders.
Newsletter, CRM and other services
Mailing list services, CRMs, booking systems and chat tools keep their own copies. Each has its own process for exporting or deleting a contact. Do it there and note what you did. For a newsletter, it is common to keep a suppressed or unsubscribed record so you do not email the person again; record why if you do.
Your inbox and email logs
Search your mailbox for the address: contact form notifications, replies and attachments. Delete or export as the request requires. If you use an SMTP or email logging plugin, its log table holds copies of messages sent to the person; it should have appeared in the Step 1 search.
Backups and logs
You usually cannot edit old backups. A common approach is to let them expire on their normal schedule and keep a record of erased addresses so that, if you ever restore a backup, you re-run the erasure. Server access logs record IP addresses rather than email addresses and are typically rotated automatically. Whether this approach satisfies the law that applies to you is a question for your adviser.
Step 6: Document the request
Keep a short record for every request, containing only what you need to show you handled it:
- date received and how;
- type of request and how identity was verified;
- each system searched and what was found;
- actions taken, including any data retained and the reason;
- date you responded and how.
The completed request in the WordPress list is itself part of that record. List all requests over SSH:
wp post list --post_type=user_request --post_status=request-pending,request-confirmed,request-failed,request-completed --fields=ID,post_title,post_name,post_status,post_date
No placeholders here. --post_type=user_request selects privacy requests; --post_status lists all four request statuses; --fields shows the ID, email address (post_title), request type (post_name), status and date. Good output shows each request with a sensible status. Old Pending or Failed rows are abandoned requests you can follow up or delete with the bulk action “Delete requests”.
Advanced: checking which exporters and erasers are registered
Designers maintaining client sites can check exactly which plugins participate before a real request arrives. Over SSH, list registered exporters:
wp eval 'foreach ( apply_filters( "wp_privacy_personal_data_exporters", array() ) as $id => $e ) { echo $id, " : ", $e["exporter_friendly_name"], PHP_EOL; }'
This runs read-only PHP inside WordPress. wp eval executes the code after WordPress loads; the filter wp_privacy_personal_data_exporters is the same one the export screen uses. Good output lists IDs such as wordpress-user, wordpress-comments, wordpress-media and, on a shop, woocommerce-customer-orders, plus any plugin exporters. Swap the filter name for wp_privacy_personal_data_erasers and the key for eraser_friendly_name to list erasers. A plugin that stores personal data but is missing from both lists will need manual handling every time. Some plugins register only in the admin context; if one you expected is missing, confirm by running a test export on your own address.
Compare these lists with the tables found by wp db search on a test address. Any table with matches but no corresponding exporter is a gap. Write it into a per-site request checklist so whoever handles the next request knows to check that plugin manually.
Developers adding their own data stores can register exporters and erasers with the same filters; the WordPress Plugin Handbook documents the expected return format, including the done flag used for paging through large data sets.
When to contact your host
Open a ticket with your host if export files cannot be created (missing zip extension, permissions or disk space), if confirmation emails never send despite correct settings, or if you need to know how long backups and server logs are kept. Include:
- the exact error text and the time you saw it, in UTC;
- the relevant lines from the PHP error log around that time;
- the result of a test export on your own address;
- what you need: enabling the PHP zip extension, a permissions fix, or retention details for backups and logs.
For tools to check headers and email delivery, see the diagnostic tools page. If the site itself is broken, start at the Emergency Room.