rylxes / laravel-gdpr
GDPR and CCPA compliance toolkit for Laravel with data export, right to erasure, consent management, and audit trails
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/events: ^10.0|^11.0|^12.0
- illuminate/filesystem: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/mail: ^10.0|^11.0|^12.0
- illuminate/notifications: ^10.0|^11.0|^12.0
- illuminate/queue: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
Requires (Dev)
- mockery/mockery: ^1.5
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpunit/phpunit: ^10.0|^11.0
README
GDPR and CCPA compliance toolkit for Laravel applications. Provides data export (portability), right to erasure, consent management, and audit trails in a single package.
Features
- Data Export (Portability) - Queue-backed export of user data as JSON, CSV, or XML with secure timed download links
- Right to Erasure - Orchestrated deletion or anonymisation respecting foreign key dependencies
- Consent Management - Audit-ready consent log with IP, user-agent, and version tracking
- Cooling-Off Period - Configurable delay before erasure execution, allowing cancellation
- Artisan Commands -
gdpr:export,gdpr:erase,gdpr:prunefor compliance officer workflows - Consent Middleware - Gate routes by consent type with
gdpr.consent:marketing - Signed Download Links - Time-limited, tamper-proof URLs via Laravel's signed routes
- CCPA Compatible - "Do not sell" opt-out support via the consent type system
- Event System -
DataExported,DataErased,ConsentRecorded,ErasureRequestedevents - Retention Policies - Configurable auto-cleanup for exports and audit logs
- Polymorphic Users - Works with any authenticatable model, not just
App\Models\User - Facade & Trait API - Use
Gdpr::export($user)or$user->recordConsent('marketing')
Installation
1. Install via Composer
composer require rylxes/laravel-gdpr
2. Run the installer
php artisan gdpr:install
This publishes the configuration file and runs migrations.
3. Implement contracts on your models
use Rylxes\Gdpr\Contracts\Exportable; use Rylxes\Gdpr\Contracts\Deletable; use Rylxes\Gdpr\Concerns\HandlesGdpr; class User extends Authenticatable implements Exportable, Deletable { use HandlesGdpr; public function exportData(): array { return $this->only(['name', 'email', 'phone', 'created_at']); } public function eraseData(): void { $this->anonymise(['name', 'email', 'phone', 'address']); } }
Apply Exportable and Deletable to any model containing personal data:
class Order extends Model implements Exportable, Deletable { use HandlesGdpr; public function exportData(): array { return $this->only(['id', 'total', 'status', 'created_at']); } public function eraseData(): void { $this->anonymise(['shipping_address', 'billing_address']); } // Child records erased before parent (lower priority = erased first) public function erasurePriority(): int { return 50; } }
Usage
Data Export
use Rylxes\Gdpr\Facades\Gdpr; // Dispatch an export job (user gets email with download link) $export = Gdpr::export($user); $export = Gdpr::export($user, 'csv'); // CSV format // Via Artisan php artisan gdpr:export 42 php artisan gdpr:export 42 --format=csv php artisan gdpr:export 42 --sync // Run synchronously
Right to Erasure
// Initiate erasure with cooling-off period $request = Gdpr::erase($user); $request = Gdpr::erase($user, 'delete', 'User requested account deletion'); // Cancel during cooling-off $request->cancel('User changed their mind'); // Via Artisan php artisan gdpr:erase 42 php artisan gdpr:erase 42 --force // Skip cooling-off php artisan gdpr:erase 42 --strategy=delete
Consent Management
// Record consent $user->recordConsent('marketing', '1.0', $request->ip()); $user->recordConsent('analytics'); // Or via facade Gdpr::recordConsent($user, 'terms_of_service', $request->ip()); // Check consent $user->hasConsent('marketing'); // true/false Gdpr::hasConsent($user, 'marketing'); // true/false // Revoke consent $user->revokeConsent('marketing'); // Get all active consent types $user->activeConsentTypes(); // ['analytics', 'terms_of_service'] // Query consent logs $user->consentLogs()->active()->get();
Consent Middleware
Gate routes that require specific consent:
Route::middleware('gdpr.consent:marketing')->group(function () { Route::get('/promotional-offers', [OffersController::class, 'index']); }); Route::middleware('gdpr.consent:analytics,tracking')->group(function () { // Requires both analytics AND tracking consent });
Data Cleanup
// Prune expired exports and old audit logs php artisan gdpr:prune php artisan gdpr:prune --force // Skip confirmation // Schedule automatic pruning (in app/Console/Kernel.php) $schedule->command('gdpr:prune --force')->daily();
Configuration
Publish the config file:
php artisan vendor:publish --tag=gdpr-config
Key Configuration Options
| Option | Default | Description |
|---|---|---|
export.default_format |
json |
Default export format (json, csv, xml) |
export.storage_disk |
local |
Filesystem disk for export files |
export.download_link_expiry_minutes |
60 |
Download link lifetime |
erasure.strategy |
anonymize |
Default: anonymize or delete |
erasure.cooling_off_days |
14 |
Days before erasure executes |
consent.version |
1.0 |
Current consent version |
consent.log_ip_address |
true |
Log IP with consent events |
queue.enabled |
true |
Queue export/erasure jobs |
queue.queue_name |
gdpr |
Queue name for GDPR jobs |
audit.consent_logs_retention_days |
2555 |
~7 years retention |
Per-Model Strategy Overrides
// config/gdpr.php 'erasure' => [ 'strategy' => 'anonymize', // default 'model_strategies' => [ App\Models\Comment::class => 'delete', App\Models\Order::class => 'anonymize', ], ],
Environment Variables
GDPR_ENABLED=true GDPR_QUEUE_ENABLED=true GDPR_QUEUE_NAME=gdpr GDPR_ERASURE_STRATEGY=anonymize GDPR_COOLING_OFF_DAYS=14 GDPR_EXPORT_FORMAT=json GDPR_DOWNLOAD_EXPIRY=60 GDPR_CONSENT_VERSION=1.0 GDPR_LOG_IP=true GDPR_CCPA_ENABLED=false
Events
Listen to GDPR events for custom integrations:
| Event | When |
|---|---|
DataExported |
After a data export is completed |
DataErased |
After user data has been erased |
ConsentRecorded |
When a user gives consent |
ErasureRequested |
When an erasure request is created |
// EventServiceProvider protected $listen = [ \Rylxes\Gdpr\Events\DataErased::class => [ \App\Listeners\NotifyDpoOfErasure::class, ], ];
Database Schema
| Table | Purpose |
|---|---|
gdpr_consent_logs |
Consent events with timestamps, IP, and version |
gdpr_erasure_requests |
Erasure request lifecycle and audit trail |
gdpr_data_exports |
Export records with download tokens and status |
All tables use a configurable prefix (gdpr_ by default).
Testing
composer test
Local Development
Add the package as a path repository in your Laravel app's composer.json:
{
"repositories": [
{
"type": "path",
"url": "../path/to/laravel-gdpr"
}
],
"require": {
"rylxes/laravel-gdpr": "*"
}
}
Then run:
composer update rylxes/laravel-gdpr php artisan gdpr:install
Security
- Download links use Laravel's
URL::temporarySignedRoute()for tamper-proof, time-limited access - Export files are stored on a configurable disk (default:
local, not publicly accessible) - Consent logs record IP addresses for audit trail compliance
- The cooling-off period prevents accidental data loss
- All GDPR operations are logged with metadata for compliance audits
Contributing
Please see CONTRIBUTING.md for details.
License
The MIT License (MIT). Please see License File for more information.
Credits
Support
- Issues
- Discussions
- Email: rylxes@gmail.com