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:
- Immediate deletion from active databases
- Tombstone records marking data as deleted (for systems requiring audit trails)
- Backup cleanup on next backup cycle
- 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()
Legal Holds
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:
- Regulatory changes: New data protection laws may require adjustments
- Business needs: Changes in your product may introduce new data types
- Technical capabilities: New storage solutions may change cost considerations
- Risk tolerance: Your organization’s position on data risk may evolve
Document policy changes with version control and maintain a changelog showing when retention periods changed and why.
Related Articles
- Coffee Meets Bagel Data Retention Policy How Long The App Ke
- Data Retention Policy Template for Startups
- GDPR Compliant Data Backup Retention Guide
- Data Processing Agreement Template for Third Party Vendors
- GDPR Data Processing Agreement Template Guide
Built by theluckystrike — More at zovo.one