The Storage Limitation Principle
GDPR Article 5(1)(e) requires that personal data be "kept in a form which permits identification of data subjects for no longer than is necessary for the purposes for which the personal data are processed."
In plain English: delete data you no longer need. This is the storage limitation principle, and it's one of the most consistently overlooked aspects of GDPR compliance.
Why Retention Policies Matter Beyond Compliance
Keeping data longer than necessary isn't just a compliance risk — it's a security risk. Every record you hold is a potential liability in a data breach. Organisations that have been breached have faced significantly higher fines when regulators found they were holding data far beyond any reasonable retention period.
There's also a practical benefit: smaller databases are faster databases.
Building a Retention Schedule
A retention schedule maps each data category to a retention period and action on expiry. Here's a starting template for WordPress/WooCommerce sites:
| Data Category | Retention Period | Legal Basis for Retention | Action on Expiry |
|---|---|---|---|
| Order records | 7 years | Tax obligation (HMRC) | Anonymise customer details |
| Customer accounts (inactive) | 2 years from last login | Legitimate interests | Delete |
| Contact form submissions | 1 year | Legitimate interests | Delete |
| Newsletter subscriptions | Until withdrawal | Consent | Delete + unsubscribe |
| Comments | 2 years | Legitimate interests | Anonymise author details |
| Analytics sessions | 14 months | Legitimate interests | Purge |
| Consent records | 3 years | Legal obligation | Delete |
| Audit logs | 3 years | Legal obligation | Delete |
| DSAR case records | 3 years | Legal obligation | Delete |
Note: These are starting points, not legal advice. Your specific retention periods should be reviewed by a solicitor familiar with your industry and jurisdiction.
Legal Hold Overrides
Some data must be retained longer than your standard policy — typically because of a legal obligation or an ongoing dispute. Common legal holds:
- Tax records — 7 years (UK, EU varies by country)
- Employment records — varies significantly by jurisdiction
- Litigation hold — data relevant to pending or anticipated litigation must be preserved
Your retention policy must accommodate legal holds without simply excluding all such data from automated deletion.
Implementing Legal Holds
// DPOKit hook: add a legal hold check
add_filter('dpokit_retention_should_delete', function($should_delete, $record) {
// Prevent deletion of orders within UK tax retention window
if ($record['type'] === 'woocommerce_order') {
$order_date = new DateTime($record['created_at']);
$tax_cutoff = new DateTime('-7 years');
if ($order_date > $tax_cutoff) {
return false; // Legal hold active
}
}
return $should_delete;
}, 10, 2);
Automating Retention Enforcement
Manual deletion of expired data is unreliable. The solution is automated scheduled jobs that identify and act on expired records.
The Dry-Run Pattern
Before running any automated deletion, always implement a dry-run mode that shows you what would be affected:
# DPOKit WP-CLI: preview what retention enforcement would do
wp dpokit retention run --dry-run
# Output:
# Would anonymise: 847 WooCommerce orders (outside tax window)
# Would delete: 1,203 contact form submissions (>1 year old)
# Would delete: 89 inactive user accounts (>2 years inactive)
# Legal hold active for: 2,341 orders (within 7-year tax window)
Once you're satisfied with the preview, run without --dry-run to execute.
Rate Limiting Large Operations
Bulk deletion on a database with millions of records can cause serious performance problems. Always chunk large operations:
// Process in batches of 500 records to avoid timeout
function delete_expired_form_submissions() {
$batch_size = 500;
$deleted = 0;
do {
$ids = get_form_submissions_older_than('1 year', $batch_size);
if (empty($ids)) break;
foreach ($ids as $id) {
delete_form_submission($id);
$deleted++;
}
// Yield to avoid PHP timeout
if ($deleted % $batch_size === 0) {
sleep(1);
}
} while (count($ids) === $batch_size);
return $deleted;
}
Audit Logging
Every automated deletion must be logged with:
- Timestamp of deletion
- Data category and count of records affected
- The retention policy rule that triggered deletion
- The action taken (delete vs anonymise)
- Who or what system initiated the action
These audit logs are themselves personal data and subject to their own retention period (typically 3 years).
Tamper-Evident Logging
For regulatory compliance, your audit log should be tamper-evident. One approach is a hash chain:
function append_audit_log($entry) {
$previous_hash = get_last_log_hash();
$entry['previous_hash'] = $previous_hash;
$entry['hash'] = hash('sha256', json_encode($entry));
// Store the log entry
wpdb_insert_log_entry($entry);
return $entry['hash'];
}
Any modification to a previous log entry will invalidate all subsequent hashes, making tampering detectable.
Communicating Retention to Users
Your privacy policy must disclose your retention periods. Be specific — "we keep your data as long as necessary" doesn't meet the transparency requirements of GDPR Article 13.
DPOKit's auto-generated privacy notice draft pulls your configured retention periods directly into the privacy notice template, keeping documentation in sync with your actual policy.
Getting Started
- Audit what data you hold (use DPOKit's scan, or do it manually)
- Define retention periods for each category (use the table above as a starting point)
- Document your schedule in your ROPA
- Implement automated enforcement (DPOKit or custom WP-Cron jobs)
- Test with a dry-run before live enforcement
- Review annually — business needs and regulations change
James Okafor
Frontend Engineer