PHPackages                             ginkelsoft/laravel-data-subject-access - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ginkelsoft/laravel-data-subject-access

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

ginkelsoft/laravel-data-subject-access
======================================

A Laravel package that implements GDPR art. 15 / 20 subject-access (inzageverzoek) exports across configured Eloquent models, in JSON or Markdown, with a tamper-evident access log.

v1.0.0(1mo ago)00[1 issues](https://github.com/ginkelsoft-development/laravel-data-subject-access/issues)1MITPHPPHP ^8.2CI passing

Since May 28Pushed 1mo agoCompare

[ Source](https://github.com/ginkelsoft-development/laravel-data-subject-access)[ Packagist](https://packagist.org/packages/ginkelsoft/laravel-data-subject-access)[ RSS](/packages/ginkelsoft-laravel-data-subject-access/feed)WikiDiscussions development Synced 1w ago

READMEChangelog (1)Dependencies (10)Versions (3)Used By (1)

Ginkelsoft Laravel Data Subject Access
======================================

[](#ginkelsoft-laravel-data-subject-access)

[![Tests](https://github.com/ginkelsoft-development/laravel-data-subject-access/actions/workflows/tests.yml/badge.svg?branch=development)](https://github.com/ginkelsoft-development/laravel-data-subject-access/actions/workflows/tests.yml)[![License](https://camo.githubusercontent.com/6c711032aff1ca0eb6b211aa6cb3649ce7fd64a7714e1181d4bb457f9680e7cf/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![Laravel](https://camo.githubusercontent.com/255077649ac4ed79e446c4d860d1c53c9764b8ff855456caeb401faf7d250205/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d31302d2d31332d627269676874677265656e3f7374796c653d666c61742d737175617265266c6f676f3d6c61726176656c)](https://laravel.com)[![PHP](https://camo.githubusercontent.com/482d9c7691869be159b0bd042060a220a12a89396d63fa223c22b74affaf42c4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532302d2d253230382e352d626c75653f7374796c653d666c61742d737175617265266c6f676f3d706870)](https://php.net)[![PHPStan](https://camo.githubusercontent.com/fa63e0381a93ba9755a46ec197198ef973137dca1643836d06b3d6263c9aa7c8/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c2532306d61782d627269676874677265656e3f7374796c653d666c61742d737175617265)](phpstan.neon.dist)

Overview
--------

[](#overview)

Implements **GDPR art. 15 (right of access)** — and, since the output is structured, doubles as art. 20 (data portability) — for a Laravel application. Given a subject identifier, the package collects every record that any registered Eloquent model holds about that subject and renders it in JSON or Markdown. The action is strictly **read-only**: nothing is modified, deleted, or anonymized.

Every access is itself a verwerking, so each export writes one row per matched model to a dedicated, append-only `subject_access_log` hash chain built on the shared `HashChain` from `ginkelsoft/laravel-compliance-core`. The log stores only the irreversible subject hash, the source model class, a record count and the format — never the exported data, never the subject identifier.

This is the **subject access** member of the GinkelSoft compliance family.

The family
----------

[](#the-family)

PackageGDPR Article(s)Role[`laravel-compliance-core`](https://github.com/ginkelsoft-development/laravel-compliance-core)art. 5(2)Shared primitives[`laravel-data-retention`](https://github.com/ginkelsoft-development/laravel-data-retention)art. 5(1)(e)Storage limitation[`laravel-data-right-to-be-forgotten`](https://github.com/ginkelsoft-development/laravel-data-right-to-be-forgotten)art. 17Subject-driven erasure**`laravel-data-subject-access`****art. 15 + 20****Subject access — this package**[`laravel-data-consent`](https://github.com/ginkelsoft-development/laravel-data-consent)art. 6(1)(a) + 7Consent registry[`laravel-data-breach-registry`](https://github.com/ginkelsoft-development/laravel-data-breach-registry)art. 33 + 34Breach registry[`laravel-compliance-hub`](https://github.com/ginkelsoft-development/laravel-compliance-hub)art. 5(2)UmbrellaHow it works
------------

[](#how-it-works)

### 1. Declare which fields are exportable

[](#1-declare-which-fields-are-exportable)

Mirror of the other family patterns: an attribute for the subject column, a property for the explicit list of fields. Models implement `Ginkelsoft\DataSubjectAccess\Contracts\Exportable` and use the matching trait.

**The field list is opt-in per field**: auto-including every column is unsafe (internal flags, technical foreign keys, hashed values) so this package refuses to do it.

```
use Ginkelsoft\DataSubjectAccess\Attributes\Exportable;
use Ginkelsoft\DataSubjectAccess\Concerns\Exportable as ExportableTrait;
use Ginkelsoft\DataSubjectAccess\Contracts\Exportable as ExportableContract;

#[Exportable(column: 'id')]
class User extends Model implements ExportableContract
{
    use ExportableTrait;

    protected array $exportable = [
        'fields' => [
            'id'    => 'Subject identifier',
            'email' => 'E-mailadres',
        ],
    ];
}

class Profile extends Model implements ExportableContract
{
    use ExportableTrait;

    protected array $exportable = [
        'column' => 'user_id',
        'fields' => [
            'first_name'    => 'Voornaam',
            'last_name'     => 'Achternaam',
            'email'         => ['label' => 'E-mailadres'],
            'logged_in_at'  => [
                'label'     => 'Aangemeld op',
                'transform' => fn ($v) => $v?->format('Y-m-d H:i:s'),
            ],
            // internal_note is intentionally not listed: it stays out of the export.
        ],
    ];
}
```

### 2. Register the models

[](#2-register-the-models)

```
// config/subject-access.php
return [
    'models' => [
        \App\Models\User::class,
        \App\Models\Profile::class,
    ],
    'include_soft_deleted' => true,
];
```

### 3. Run the export

[](#3-run-the-export)

```
php artisan retention:export 01HXYZ
php artisan retention:export 01HXYZ --format=markdown
php artisan retention:export 01HXYZ --format=json --output=storage/exports/01HXYZ.json
```

The command name keeps the `retention:` prefix for BC with the v1.x monolithic package. Without `--output` the export is written to STDOUT, so it can be piped or captured. With `--output` it lands in the given file (missing intermediate directories are created). Two formats ship by default; the `Ginkelsoft\DataSubjectAccess\Contracts\Exporter` interface lets you add more (HTML, CSV, PDF) without touching the rest of the package.

### 4. Verify the access chain

[](#4-verify-the-access-chain)

```
use Ginkelsoft\ComplianceCore\Config\LogSecret;
use Ginkelsoft\ComplianceCore\Support\HashChain;
use Illuminate\Support\Facades\DB;

$entries = DB::table('subject_access_log')->orderBy('id')->get()
    ->map(fn ($row) => (array) $row)->all();

$intact = HashChain::verify($entries, LogSecret::value());
```

Or run `php artisan compliance:verify` from the [hub](https://github.com/ginkelsoft-development/laravel-compliance-hub) to verify every chain in the family in one shot.

What the log stores
-------------------

[](#what-the-log-stores)

ColumnMeaning`subject_hash`SHA-256 of subject id + secret — irreversible`model_type`FQCN of the matched model`record_count`Number of records found for this model`format`The exporter format used (`json`, `markdown`, ...)`performed_at`UTC timestamp`previous_hash` / `hash`Hash chain bookkeepingNo subject identifier, no field values.

Compliance notes
----------------

[](#compliance-notes)

- **GDPR art. 15** — Right of access. Produces a structured snapshot of every Exportable record per subject.
- **GDPR art. 20** — Data portability. The JSON format is machine-readable and reusable by the subject.
- **GDPR art. 5(2)** — Accountability. The `subject_access_log` hash chain proves the access happened, when, by which format, and over which models.

This package is **not legal advice**. Identity verification (is this requester actually the subject?) is your application's responsibility.

Installation
------------

[](#installation)

```
composer require ginkelsoft/laravel-data-subject-access
php artisan vendor:publish --tag=compliance-config
php artisan vendor:publish --tag=subject-access-config
php artisan vendor:publish --tag=subject-access-migrations
php artisan migrate
```

Then add a secret to `.env` (shared with the rest of the family):

```
COMPLIANCE_LOG_SECRET="$(openssl rand -base64 32)"
```

Gotchas
-------

[](#gotchas)

- **Identity verification is your problem.** This package does not check whether the requester is actually the subject. Running `retention:export`against an unverified identifier is a data breach waiting to happen — do a verified email round-trip, an authenticated session, or a manual KYC step before invoking the command.
- **The export is a snapshot.** Records created or modified after the export are obviously not in it. If the subject asks for a fresh export tomorrow, run it again — accountability comes from the per-call log row.
- **Trait conflict with right-to-be-forgotten.** A model that carries both `Exportable` (this package) and `Forgettable` (`laravel-data-right-to-be-forgotten`) must resolve the `forSubjectQuery` conflict explicitly: ```
    use Ginkelsoft\DataRightToBeForgotten\Concerns\Forgettable;
    use Ginkelsoft\DataSubjectAccess\Concerns\Exportable;
    class User extends Model implements ExportableContract, ForgettableContract
    {
        use Exportable, Forgettable {
            Forgettable::forSubjectQuery insteadof Exportable;
        }
    }
    ```

    When both policies use the same subject column (the common case), either `insteadof` picks works. When the columns differ, override `forSubjectQuery` on the model directly instead.
- **PDF is intentionally not built-in.** Adding a PDF generator would pull in a heavy dependency for what is essentially an Exporter contract that you can implement in a project-specific way (Dompdf, mPDF, Browsershot). JSON and Markdown cover the common cases.
- **Migrated from v1.x?** In the monolithic `laravel-data-retention` v1.x package, subject-access logged into `retention_log` with a sentinel `subject_access` field. This package writes to its own `subject_access_log` table instead, which means existing `retention_log` rows from before the upgrade stay verifiable in place (the retention package still owns that chain) and new access actions start a fresh chain in the new table. No data migration needed; see [UPGRADE.md](https://github.com/ginkelsoft-development/laravel-compliance-core/blob/development/UPGRADE.md).

Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
vendor/bin/phpstan analyse --memory-limit=1G
vendor/bin/pint --test
```

Reporting bugs
--------------

[](#reporting-bugs)

Found a bug or unexpected behaviour? We want to hear about it.

**Preferred — open a GitHub issue:**

When opening an issue, please include:

1. **Versions** — PHP, Laravel, and the package version (`composer show ginkelsoft/laravel-data-subject-access`).
2. **What you did** — the artisan command, code snippet, or steps that triggered the bug.
3. **What you expected** vs **what actually happened** — include full error output or a stack trace if there is one.
4. **A minimal reproduction** if you can — a failing test or a small code sample beats a long description.

**Security-sensitive findings** (anything that could expose personal data, break a hash-chain, or bypass an audit log) — please **do not**open a public issue. E-mail **** directly with "SECURITY" in the subject line and we will respond privately.

**Not on GitHub?** You can also e-mail **** with the same information.

Contact
-------

[](#contact)

For commercial support, integration questions, or anything that doesn't fit a GitHub issue: **** — .

License
-------

[](#license)

MIT License — see [LICENSE](LICENSE). (c) 2026 Ginkelsoft

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance89

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

57d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/165766253?v=4)[ginkelsoft](/maintainers/ginkelsoft)[@GinkelSoft](https://github.com/GinkelSoft)

---

Top Contributors

[![ginkelsoft-development](https://avatars.githubusercontent.com/u/179240029?v=4)](https://github.com/ginkelsoft-development "ginkelsoft-development (3 commits)")

---

Tags

laravelgdprcomplianceaudit-logdata-portabilityavgginkelsoftsubject-accessright-of-accessinzageverzoek

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ginkelsoft-laravel-data-subject-access/health.svg)

```
[![Health](https://phpackages.com/badges/ginkelsoft-laravel-data-subject-access/health.svg)](https://phpackages.com/packages/ginkelsoft-laravel-data-subject-access)
```

###  Alternatives

[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k30.2M151](/packages/laravel-cashier)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k15.1M136](/packages/laravel-pulse)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M246](/packages/laravel-ai)[flarum/core

Delightfully simple forum software.

211.4M2.4k](/packages/flarum-core)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
