PHPackages                             ginkelsoft/laravel-data-right-to-be-forgotten - 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. [Security](/categories/security)
4. /
5. ginkelsoft/laravel-data-right-to-be-forgotten

ActiveLibrary[Security](/categories/security)

ginkelsoft/laravel-data-right-to-be-forgotten
=============================================

A Laravel package that implements GDPR art. 17 right-to-be-forgotten across configured Eloquent models, with per-model delete/anonymize strategies and a tamper-evident audit log.

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

Since May 28Pushed 1mo agoCompare

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

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

Ginkelsoft Laravel Data Right to Be Forgotten
=============================================

[](#ginkelsoft-laravel-data-right-to-be-forgotten)

[![Tests](https://github.com/ginkelsoft-development/laravel-data-right-to-be-forgotten/actions/workflows/tests.yml/badge.svg?branch=development)](https://github.com/ginkelsoft-development/laravel-data-right-to-be-forgotten/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. 17 — the right to be forgotten** for a Laravel application. When a subject ("forget everything about me") files a request, this package sweeps every model you have registered as containing personal data for that subject and either deletes or anonymizes the matching records. Every action is recorded in a tamper-evident `forget_log` chain built on the shared `HashChain` from `ginkelsoft/laravel-compliance-core`.

This is the **right to be forgotten** member of the GinkelSoft compliance family — install the hub to get the whole family in one go.

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

[](#the-family)

PackageGDPR Article(s)Role[`laravel-compliance-core`](https://github.com/ginkelsoft-development/laravel-compliance-core)art. 5(2)Shared primitives (hash chain, strategies, subject hash)[`laravel-data-retention`](https://github.com/ginkelsoft-development/laravel-data-retention)art. 5(1)(e)Storage limitation / time-based sweeps**`laravel-data-right-to-be-forgotten`****art. 17****Subject-driven erasure — this package**[`laravel-data-subject-access`](https://github.com/ginkelsoft-development/laravel-data-subject-access)art. 15 + 20Read-only subject export[`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 + 34Personal-data breach register[`laravel-compliance-hub`](https://github.com/ginkelsoft-development/laravel-compliance-hub)art. 5(2)Umbrella: installs the whole family, verifies every chainWhy a separate package from retention?
--------------------------------------

[](#why-a-separate-package-from-retention)

Time-based retention answers "is this data old enough to remove?". GDPR art. 17 answers a different question: "this specific person has asked me to remove **everything** about them, today." The two controls share building blocks (delete vs. anonymize, per-field strategies, append-only audit log) but the trigger and the audit-chain are different — retention writes to `retention_log`, forget writes to `forget_log`. Keeping them in separate packages keeps each chain internally consistent and independently auditable.

How it works
------------

[](#how-it-works)

### 1. Declare which models hold subject data

[](#1-declare-which-models-hold-subject-data)

An attribute for simple cases, a property for anonymize. Models must additionally implement the `Forgettable` contract — the trait provides the default implementation, the interface gives the orchestrator the type safety it needs.

```
use Ginkelsoft\DataRightToBeForgotten\Attributes\Forgettable;
use Ginkelsoft\DataRightToBeForgotten\Concerns\Forgettable as ForgettableTrait;
use Ginkelsoft\DataRightToBeForgotten\Contracts\Forgettable as ForgettableContract;

#[Forgettable(column: 'id', action: 'delete')]
class User extends Model implements ForgettableContract
{
    use ForgettableTrait;
}

#[Forgettable(column: 'user_id', action: 'delete')]
class Order extends Model implements ForgettableContract
{
    use ForgettableTrait;
}

class Profile extends Model implements ForgettableContract
{
    use ForgettableTrait;

    protected array $forgettable = [
        'column'    => 'user_id',
        'action'    => 'anonymize',
        'anonymize' => [
            'first_name' => 'placeholder',
            'last_name'  => 'placeholder',
            'email'      => 'hash',
        ],
    ];
}
```

For complex subject mappings (subject can appear in either of two columns, polymorphic relation, etc) override the static `forSubjectQuery` method on the model. See `tests/Models/ForgetTicket.php` for an OR-across-two-columns example.

### 2. Register the models

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

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

### 3. Run the sweep

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

```
php artisan retention:forget 01HXYZ --dry-run
php artisan retention:forget 01HXYZ
```

The command name keeps the `retention:` prefix for BC with the v1.x monolithic package. The first argument is the subject identifier: whatever string consistently identifies the person across your models. The orchestrator iterates every registered model and applies its policy to records linked to that subject. Idempotent: a second run finds no new records and writes no new log entries.

### 4. Verify the audit chain

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

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

$entries = DB::table('forget_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 holds (and what it does not)
-----------------------------------------

[](#what-the-log-holds-and-what-it-does-not)

The `forget_log` rows contain `subject_hash` (an irreversible SHA-256 of the subject identifier plus `compliance.log_secret`), the source model class and primary key, the action (`forgotten_deleted` or `forgotten_anonymized`), timestamps, and the chain hashes. **No subject identifier, no field values — just the proof that the person was forgotten.**

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

[](#compliance-notes)

- **GDPR art. 17** — Right to erasure ("right to be forgotten"). This package gives you a documented, automated mechanism to honour an erasure request, and a tamper-evident record of every action taken.
- **GDPR art. 5(2)** — Accountability. The `forget_log` hash chain is the evidence.

This package is **not legal advice**. The decision to delete vs. anonymize per field belongs to your DPO.

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

[](#installation)

```
composer require ginkelsoft/laravel-data-right-to-be-forgotten
php artisan vendor:publish --tag=compliance-config
php artisan vendor:publish --tag=forget-config
php artisan vendor:publish --tag=forget-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)

- **Not transitively cascaded.** Only models in `forget.models` are touched. If `Order` is forgotten but `OrderLine` is not in the list, the order lines remain — give them their own Forgettable policy if they hold personal data.
- **Trait conflict with subject-access.** If a model carries both `Forgettable` and `Exportable` (from `laravel-data-subject-access`), PHP requires explicit conflict resolution because both traits define `forSubjectQuery`. The two defaults are functionally identical when both policies use the same subject column, so picking one with `insteadof`suffices: ```
    use Ginkelsoft\DataRightToBeForgotten\Concerns\Forgettable;
    use Ginkelsoft\DataSubjectAccess\Concerns\Exportable;
    class User extends Model implements ExportableContract, ForgettableContract
    {
        use Exportable, Forgettable {
            Forgettable::forSubjectQuery insteadof Exportable;
        }
    }
    ```

    If the two policies need DIFFERENT columns, override `forSubjectQuery` on the model itself instead of using `insteadof`.
- **Backups and warehouse copies are out of scope.** Document that separately in your DPIA.
- **Re-creation after forget is application logic.** If your app re-fills a profile for the same subject after a forget, that is your bug to fix; the package only records the forget that happened.

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-right-to-be-forgotten`).
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-loganonymizeavgginkelsoftforgetright-to-be-forgottenerasure

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/ginkelsoft-laravel-data-right-to-be-forgotten/health.svg)

```
[![Health](https://phpackages.com/badges/ginkelsoft-laravel-data-right-to-be-forgotten/health.svg)](https://phpackages.com/packages/ginkelsoft-laravel-data-right-to-be-forgotten)
```

###  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)[laravel/ai

The official AI SDK for Laravel.

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

PHPackages © 2026

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