Retention Enforcement

Define retention policies, schedule automated deletion, and maintain a tamper-evident audit log.

4 min read

Retention Enforcement

DPOKit's retention module automates the lifecycle of personal data — from initial collection through to deletion or anonymisation — and keeps an immutable audit log of every action.

Retention policy configuration screen showing data categories and their configured periods

Configuring retention policies

Go to DPOKit → Retention → Policies to define periods per data category:

Data categoryDefault periodConfigurable?
WooCommerce orders7 yearsYes
Form submissions2 yearsYes
WordPress user accountsUntil account deletedYes
WordPress comments3 yearsYes
Consent records3 yearsYes
Audit log3 yearsYes

Legal hold rules

Legal holds override standard retention periods. Example:

  • Tax retention hold: retain orders for 7 years regardless of any shorter policy
  • Litigation hold: retain specified records indefinitely until the hold is lifted

Holds are configured at Retention → Legal Holds and require Administrator capability to create or modify.

Legal holds configuration screen showing active holds with their reason and expiry date

Registering a custom retention policy

Plugin authors can register their own data categories and retention periods:

add_filter( 'pv_retention_policies', function( $policies ) {
    $policies['my_plugin_submissions'] = [
        'label'          => 'My Plugin Submissions',
        'default_days'   => 365,       // 1 year
        'min_days'       => 30,
        'max_days'       => 2555,      // 7 years
        'action'         => 'anonymise',  // 'delete' | 'anonymise' | 'flag'
        'query_callback' => 'my_plugin_get_expired_records',
        'action_callback'=> 'my_plugin_anonymise_records',
    ];
    return $policies;
} );
 
/**
 * Return IDs of records older than $before_date.
 *
 * @param  string $before_date ISO 8601 date string.
 * @return int[]
 */
function my_plugin_get_expired_records( string $before_date ): array {
    global $wpdb;
    return $wpdb->get_col(
        $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}my_submissions WHERE created_at < %s",
            $before_date
        )
    );
}
 
/**
 * Anonymise a batch of records by ID.
 *
 * @param  int[] $ids
 * @return int   Number of records affected.
 */
function my_plugin_anonymise_records( array $ids ): int {
    global $wpdb;
    $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
    return $wpdb->query(
        $wpdb->prepare(
            "UPDATE {$wpdb->prefix}my_submissions
             SET email = 'anonymised@example.com', name = 'Anonymised', ip = ''
             WHERE id IN ({$placeholders})",
            ...$ids
        )
    );
}

Hooking into enforcement events

// Fired after each enforcement batch completes
add_action( 'pv_retention_batch_complete', function( $policy_id, $action, $count ) {
    error_log( "Retention: {$action} {$count} records for policy '{$policy_id}'" );
}, 10, 3 );
 
// Fired when a record is skipped due to a legal hold
add_action( 'pv_retention_legal_hold_applied', function( $policy_id, $record_id, $hold_id ) {
    // Custom notification or logging
}, 10, 3 );

Automated enforcement

DPOKit runs a retention enforcement job via WP-Cron on a configurable schedule (default: daily at 02:00).

For each data category, the job:

  1. Queries records older than the configured retention period
  2. Checks for legal hold overrides
  3. Applies the configured action: delete, anonymise, or flag for manual review
  4. Writes an entry to the immutable audit log

Dry-run mode

Before running enforcement, you can preview what would be affected without making any changes:

  1. Go to Retention → Run Enforcement.
  2. Enable Dry-run mode.
  3. Click Preview — a report shows the record counts and actions that would be taken.

Dry-run preview report showing affected record counts per data category before enforcement

Rate limiting

Large-scale enforcement jobs are chunked to avoid PHP timeouts and database overload. The default chunk size is 500 records per batch, with a configurable delay between batches.

// Adjust batch size and delay for large databases
add_filter( 'pv_retention_batch_size', fn() => 200 );
add_filter( 'pv_retention_batch_delay_ms', fn() => 500 ); // milliseconds between batches

Audit log

Every deletion and anonymisation is recorded in the audit log at Retention → Audit Log:

FieldDescription
TimestampWhen the action was taken
Data categorye.g. woocommerce_orders
Record countHow many records were affected
Actiondeleted / anonymised / flagged
Job IDLinks related log entries for one enforcement run
HashSHA-256 of the previous entry — tamper-evident chain

Audit log screen showing timestamped entries with tamper-evident hashes

Exporting the audit log

Export the log as CSV or PDF from the Audit Log screen. Reports are timestamped and archived for the configured log retention period (default: 3 years).

WP-CLI

Run retention enforcement from the command line:

# Dry run
wp dpo-kit retention run --dry-run
 
# Live run
wp dpo-kit retention run
 
# Run for a specific data category
wp dpo-kit retention run --category=woocommerce_orders