Privacy Tools Guide

Every application that handles user data faces the same fundamental question: how long should we keep this information? A well-structured data retention policy answers that question systematically, reducing storage costs, minimizing legal exposure, and simplifying compliance with regulations like GDPR, CCPA, and HIPAA.

This guide provides a practical data retention policy template you can adapt for your projects. I’ll cover the key categories of data to consider, recommended retention periods with rationale, and implementation examples you can use in your codebase.

Why Data Retention Policies Matter

Storing data indefinitely creates unnecessary risk. Data breaches become more damaging with accumulated information. Storage costs accumulate. Compliance audits become more complex when you cannot demonstrate what data you keep and why.

A clear retention policy also helps with data subject rights requests. When users ask you to delete their data (a right under GDPR Article 17), having defined retention periods makes compliance straightforward—you either delete the data or explain why you retain it legally.

Data Retention Policy Template Structure

A practical retention policy organizes data into categories based on type and regulatory requirements. Here’s a template you can customize:

1. User Account Data

Data Type Retention Period Rationale
Account credentials Active + 30 days Allows account recovery and session cleanup
Profile information Active + 90 days Grace period for account reactivation
Account deletion request Immediate + 30 days Ensures deletion completes across all systems

2. Transaction and Activity Logs

Data Type Retention Period Rationale
Authentication logs 12 months Security incident investigation
API access logs 6 months Debugging and abuse prevention
Payment transactions 7 years (or per legal requirement) Tax and financial regulations

3. Content and Communications

Data Type Retention Period Rationale
User-generated content Active + 90 days post-deletion Allows content restoration during grace period
Support tickets 2 years Customer service records
Email communications 1 year Business records retention

4. Analytics and Aggregated Data

Data Type Retention Period Rationale
Raw analytics events 90 days Performance and storage optimization
Aggregated statistics Indefinite Business intelligence (no PII)
A/B test results 24 months Long-term product decisions

Implementing Automated Data Retention

Rather than relying on manual cleanup, integrate retention logic into your application. Here are practical examples for different scenarios.

Database Cleanup Script (Python)

from datetime import datetime, timedelta
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

def cleanup_old_logs(engine, days=180):
    """Delete authentication and API logs older than specified days."""
    cutoff = datetime.utcnow() - timedelta(days=days)

    with sessionmaker(bind=engine) as session:
        result = session.execute(
            f"DELETE FROM api_logs WHERE created_at < '{cutoff.isoformat()}'"
        )
        session.commit()
        return result.rowcount

# Run as scheduled task
if __name__ == "__main__":
    engine = create_engine("postgresql://localhost/mydb")
    deleted = cleanup_old_logs(engine, days=180)
    print(f"Cleaned up {deleted} old log entries")

SQL-Based Retention Policy

-- Create a policy table to track retention rules
CREATE TABLE data_retention_policies (
    id SERIAL PRIMARY KEY,
    table_name VARCHAR(100) NOT NULL,
    column_name VARCHAR(100) NOT NULL,
    retention_days INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Example retention policies
INSERT INTO data_retention_policies (table_name, column_name, retention_days)
VALUES
    ('api_logs', 'created_at', 180),
    ('user_sessions', 'last_activity', 30),
    ('password_reset_tokens', 'created_at', 1),
    ('authentication_logs', 'timestamp', 365);

Laravel Implementation

<?php
// app/Console/Commands/DataRetentionCommand.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Carbon\Carbon;

class DataRetentionCommand extends Command
{
    protected $signature = 'data:retention {--dry-run : Show what would be deleted}';

    public function handle()
    {
        $policies = [
            ['table' => 'api_logs', 'column' => 'created_at', 'days' => 180],
            ['table' => 'user_sessions', 'column' => 'last_activity', 'days' => 30],
            ['table' => 'password_reset_tokens', 'column' => 'created_at', 'days' => 1],
        ];

        foreach ($policies as $policy) {
            $query = \DB::table($policy['table'])
                ->where($policy['column'], '<', Carbon::now()->subDays($policy['days']));

            if ($this->option('dry-run')) {
                $count = $query->count();
                $this->info("Would delete {$count} rows from {$policy['table']}");
            } else {
                $count = $query->delete();
                $this->info("Deleted {$count} rows from {$policy['table']}");
            }
        }
    }
}

Run this as a scheduled task:

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->command('data:retention')->dailyAt('3:00am');
}

Handling Special Cases

Data Subject Deletion Requests

When processing GDPR deletion requests, you must remove data from all systems including backups. The practical approach involves:

  1. Immediate deletion from active databases
  2. Tombstone records marking data as deleted (for systems requiring audit trails)
  3. Backup cleanup on next backup cycle
  4. Third-party notification if data was shared with processors
def process_deletion_request(user_id):
    """Handle a complete user data deletion request."""
    tables = ['users', 'user_profiles', 'api_tokens', 'sessions']

    with sessionmaker(bind=engine) as session:
        for table in tables:
            session.execute(f"DELETE FROM {table} WHERE user_id = {user_id}")

        # Create tombstone for audit purposes
        session.execute(
            "INSERT INTO deletion_tombstones (user_id, deleted_at) VALUES (%s, NOW())",
            (user_id,)
        )
        session.commit()

Sometimes retention periods must be extended due to legal investigations or compliance requirements. Implement a legal hold system:

CREATE TABLE legal_holds (
    id SERIAL PRIMARY KEY,
    user_id VARCHAR(100),
    reason TEXT,
    hold_until DATE,
    created_by VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Exclude data under legal hold from cleanup
DELETE FROM api_logs
WHERE created_at < NOW() - INTERVAL '180 days'
AND user_id NOT IN (SELECT user_id FROM legal_holds WHERE hold_until > NOW());

Review and Maintain Your Policy

A data retention policy is not a set-and-forget document. Schedule quarterly reviews to ensure your policy remains aligned with:

Document policy changes with version control and maintain a changelog showing when retention periods changed and why.

Built by theluckystrike — More at zovo.one