Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Gdpr Exporter Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into the application that actually holds the personal data — not a shared package or a side service:

composer require milenmk/laravel-gdpr-exporter
php artisan vendor:publish --provider="Milenmk\GdprExporter\GdprExporterServiceProvider"

The provider string varies by package namespace; if the publish command errors, get the real one from composer show -a milenmk/laravel-gdpr-exporter or by grepping vendor/milenmk/laravel-gdpr-exporter/src/ for extends ServiceProvider.

Read three things before writing a line of application code, in this order:

  1. vendor/milenmk/laravel-gdpr-exporter/config/ — for this class of package the config is the API. It declares which models are exported, which columns and relations are pulled, and what is excluded.
  2. vendor/milenmk/laravel-gdpr-exporter/src/ — look for a contract (Exportable, Exporter) or trait models must implement, and whether generation is dispatched as a queued job or runs inline.
  3. php artisan list | grep -i gdpr — confirms the registered command name and its options (subject identifier, disk, format).

Commit the published config. That file is the auditable record of what a subject-access response contained at a given point in time — it is the document you hand a DPO, not a screenshot of a controller.

First use case: one Art. 15 request end to end. Point the config at User plus its highest-risk relations (orders, addresses, consents, login_history), create a factory user with a small but realistic graph, and run an export for that account. Then open the artifact and reconcile it against the database manually. Do this before wiring anything to a Livewire button or a support queue — the failure mode you are hunting is silent under-export, and it only shows up when you compare row counts.

Implementation Patterns

Let the config drive, not the controller. Every export path — Artisan command, queued job, Livewire action — should funnel through the same declared model map. If a support agent triggers an export and an engineer triggers one via CLI, the two artifacts must be identical. Any ->toJson() written by hand in a controller is a divergence waiting to happen.

Queue it, always. A long-tenured account with hundreds of thousands of related rows will exceed max_execution_time and memory inside an HTTP request. Wrap the export in a job and notify on completion:

class GenerateGdprExport implements ShouldQueue
{
    public function __construct(public int $userId, public int $requestId) {}

    public function handle(GdprExporter $exporter): void
    {
        $path = $exporter->for(User::findOrFail($this->userId))
                         ->toDisk('gdpr-private')
                         ->save();

        ExportRequest::whereKey($this->requestId)->update([
            'path' => $path,
            'completed_at' => now(),
        ]);

        Mail::to($this->user->email)->queue(new ExportReady($this->requestId));
    }
}

If the package ships its own job class, extend it rather than reimplementing — that keeps file-generation logic in one place across upgrades.

Custom exporters for non-standard storage. JSON columns, encrypted casts, polymorphic relations, and files in object storage do not fit a column map cleanly. If the package exposes an interface for custom sources, implement it per model rather than forking. Forking makes every minor bump (like 1.3.2 → 1.4.0) a manual merge.

Separate the export and erasure workflows even though they read the same model list. For erasure (Art. 17), most applications need redaction — null the PII, keep the row for referential integrity — not delete(). Decide per model and encode the decision in config, not in a branch inside a controller.

Audit every request. A small export_requests table (requested_by, subject_id, requested_at, completed_at, path, sha256, delivered_at, purged_at) is both an accountability requirement and your only debugging surface once the file has left your hands. Hash the artifact before delivery so you can prove later that what was sent matches what was generated.

Livewire integration. Keep the component thin: it collects the request, verifies identity, writes the audit row, dispatches the job, and polls status. It must not assemble data. A useful pattern is a Wire\Poll on completed_at that flips to a temporaryUrl() download link with a short TTL.

Test with fixtures, assert presence not equality. Build a user with a known graph, export, and assert keys and row counts exist. Full-file equality breaks on every unrelated migration and gets disabled within a month.

$this->assertArrayHasKey('consents', $export['user']);
$this->assertCount(3, $export['user']['orders']);

Gotchas and Tips

  • Published config drift is the #1 silent failure. Once published, your config stops receiving package updates. Diff it against vendor/.../config/ on every bump. A new key added upstream means your older config silently lacks it and default behaviour may differ.
  • $hidden and $casts leak into exports. Eloquent's $hidden protects API responses and array/JSON serialization alike — so a field hidden for security can vanish from the GDPR export too. Check whether the package bypasses $hidden via explicit column selection or a raw query. Conversely, $appends accessors that touch other tables will fire N+1 queries during export.
  • Under-exporting is the production failure, over-exporting is the audit finding. A relation omitted from config, a column never selected, a polymorphic type not covered — none of these throw. Add a CI test that introspects the schema and asserts every column tagged as personal data is mapped.
  • Scoping is a security boundary. Verify there is a test proving one subject's export cannot contain another's rows. Never accept a user ID from the request without resolving it through the authenticated subject, and never let a support-facing endpoint take an arbitrary ID.
  • Private disk, encryption, TTL. An export file is the entire PII footprint of one person. Use a dedicated disk (gdpr-private), never public, and purge on a 24–72h schedule with a scheduled command. Note that in Laravel 9+ the local disk roots at storage/app/private while older installs pointed at storage/app — confirm which one the package writes to before a support agent goes looking.
  • Memory on relation-heavy graphs. with() on a user with nested history loads everything at once. Prefer chunked per-relation queries and measure with memory_get_peak_usage() against a realistic large account, not a seeder.
  • Soft deletes and anonymised rows. Decide explicitly whether trashed records appear in the export. Excluding them is often a compliance gap; including them can surface data you believed was erased — which is worse to discover during a regulatory review than during testing.
  • Third-party processors are outside the package. Data in Stripe, Mailchimp, Sentry, or your object storage is invisible here. Your DSAR runbook needs a documented manual step for processor-held data; the package solves only the first-party half.
  • Empty changelogs. 1.4.0 ships only a compare link. Before upgrading a pinned install, read git log --oneline 1.3.2..1.4.0 and check whether config keys or the archive format changed — and verify composer.json PHP/Laravel constraints against your own framework upgrade horizon, since a package capped below your next major becomes a migration blocker with a legal dependency attached.
  • The package produces a file; it does not produce compliance. Lawful basis, identity verification of the requester, and the 30-day response clock are your application's responsibility.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums