milenmk/laravel-gdpr-exporter
A Laravel package for GDPR compliance that helps you export a user’s personal data from your application. Configure data sources, generate downloadable exports, and fulfill data-access requests with minimal setup.
Since this assessment has no prior baseline, treat 1.4.0 as the first version you evaluate. The 1.3.2 → 1.4.0 bump is a minor release under semver, so no breaking changes are signalled — but the changelog link is the only artifact published for this release, so skim the diff before upgrading a pinned install.
composer require milenmk/laravel-gdpr-exporter
php artisan list | grep -i gdpr — confirms the command name(s) the package registers.vendor/milenmk/laravel-gdpr-exporter/config/ — read the shipped config verbatim. In packages like this, the config is the API: it defines which models are exported, which columns/relations are pulled, and what is deliberately excluded.vendor/milenmk/laravel-gdpr-exporter/src/ — check for a contract/interface or trait that models must implement, and whether exports are dispatched as queued jobs.php artisan vendor:publish --provider="Milenmk\GdprExporter\...ServiceProvider"
Commit the published config so the exact scope of a data export is auditable — this is the document you show a DPO or regulator.User model and its relations (orders, addresses, consents, logs), then trigger an export for a test account and open the generated file end to end.Model registration over ad-hoc queries. Drive the export from the config's model map rather than adding one-off ->toJson() calls. This keeps every export path funneled through the same declaration, so a request handled by a support agent produces an identical artifact to one handled by a console command.
Queue the job, don't inline it. A user with thousands of orders/audit records will blow past PHP's max_execution_time and memory limit inside an HTTP request. Wrap the export in a queued job and email a signed, expiring download link when it completes. If the package ships its own job class, extend it rather than reimplementing; if not, keep the package's exporter callable inside your own job so the file-generation logic stays in one place.
Two distinct workflows, one config. Treat export (Art. 15/20) and erasure (Art. 17) as separate operations even if they read the same model list. For erasure, most apps need redaction (null out PII, keep the row for referential integrity) rather than row deletion — decide per model and encode that decision in config, not in controller code.
Integration with audit logging. Log every export request (who, when, subject, which artifact hash). This is both a GDPR accountability requirement and your only way to debug "the export was missing X" after the fact, because the file itself has been handed to the data subject.
Consent and retention hooks. If you already have a consent table, make sure the export includes consent history (granted/withdrawn timestamps, source) — it is one of the most commonly omitted categories in a first implementation and one of the first things a DPO asks for.
Testing pattern. Use a factory-created user with a known, minimal data graph and assert the exported structure against a fixture. Assert on presence of keys rather than full-file equality, or the test suite will break on every unrelated migration.
git log --oneline 1.3.2..1.4.0) before upgrading, particularly the config file — if a new config key was added, your published (older) config will silently lack it and default behaviour may differ.vendor/.../config/ on every minor bump; a new model or column added upstream will otherwise never appear in your exports.hidden attribute on the model, or a select that never loads the column. Validate against a real account with realistic data, not a seeder.$hidden and $casts interaction. Eloquent's $hidden protects API responses, but array/JSON exports respect it too — so a field hidden for security may vanish from the GDPR export as well. Check whether the package bypasses this via direct query or explicit column selection.public), encrypt at rest, and delete on a TTL (24–72h is typical). Confirm the package's default disk, since local in newer Laravel means storage/app/private while older installs pointed at storage/app.with() on a user with deeply nested history loads everything at once. Prefer chunked per-relation queries, and measure peak memory with memory_get_peak_usage() on a large test account.How can I help you explore Laravel packages today?