PHPackages                             graymatter/laravel-audit-chain - 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. [Database &amp; ORM](/categories/database)
4. /
5. graymatter/laravel-audit-chain

ActiveLibrary[Database &amp; ORM](/categories/database)

graymatter/laravel-audit-chain
==============================

Immutable audit trail for Laravel Eloquent models via cryptographic hash chains. GDPR &amp; NIS2 compliant.

v1.0.0(4mo ago)221MITPHPPHP ^8.2CI passing

Since Feb 20Pushed 4mo agoCompare

[ Source](https://github.com/graymattertechnology/laravel-audit-chain)[ Packagist](https://packagist.org/packages/graymatter/laravel-audit-chain)[ Docs](https://github.com/graymattertechnology/laravel-audit-chain)[ RSS](/packages/graymatter-laravel-audit-chain/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (11)Versions (2)Used By (0)

Laravel Audit Chain
===================

[](#laravel-audit-chain)

[![Latest Version on Packagist](https://camo.githubusercontent.com/1e2f0eb2d83ddf80612a51495af4e5fab3c52a732004a9b9100353510bc539b0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f677261796d61747465722f6c61726176656c2d61756469742d636861696e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/graymatter/laravel-audit-chain)[![GitHub Tests Action Status](https://camo.githubusercontent.com/a32d9a8d7f98fff09fb64e3adeb5c9048b9726e025a9b50e2a673b7e000717de/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f677261796d6174746572746563686e6f6c6f67792f6c61726176656c2d61756469742d636861696e2f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/graymattertechnology/laravel-audit-chain/actions?query=workflow%3ACI+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/f04a8372cfd57e481687023866fd1e8f1f8e0ebca51fe60b5c713a7bfe9c8135/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f677261796d61747465722f6c61726176656c2d61756469742d636861696e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/graymatter/laravel-audit-chain)

An immutable audit trail for Laravel Eloquent models with optional cryptographic hash chain verification. Built for GDPR (articles 15, 17, 33) and NIS2 (article 21) compliance.

Features
--------

[](#features)

- **Two modes**: Light activity log (`HasActivityLog`) or full cryptographic hash chain (`HasAuditTrail`)
- **Immutable audit logs**: Eloquent guards prevent updates/deletes on audit records
- **Cryptographic hash chain**: SHA-256 linked chain for tamper detection (full mode)
- **GDPR compliance**: Personal data annotation, anonymization, full subject data export
- **SoftDeletes support**: Auto-detects `SoftDeletes`, captures `restored` and `forceDeleted` events
- **Custom events**: Record business events via `$model->audit('published')`
- **Batch grouping**: Group related audit logs under a single UUID via `AuditChain::batch()`
- **Free-form metadata**: Attach context to audit logs via `AuditChain::context()`
- **Disable logging**: `AuditChain::withoutAudit()` for seeds, imports, and migrations
- **Field control**: `$auditInclude` / `$auditExclude` + auto-exclusion of `$hidden` fields
- **User agent logging**: Captures browser/client user agent automatically
- **READ event capture**: Opt-in `retrieved` event logging
- **Chain verification**: `audit:verify` command with `--notify` for cron scheduling
- **Log pruning**: `audit:prune` command with configurable retention
- **Notifications**: Mail + webhook alerts (Slack, Teams, Discord compatible)
- **Separate DB connection**: Isolate audit data from application data
- **Queue support**: Offload audit recording to background jobs

Requirements
------------

[](#requirements)

- PHP &gt;= 8.2
- Laravel 11 or 12

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

[](#installation)

```
composer require graymatter/laravel-audit-chain
```

Publish the config and migration:

```
php artisan vendor:publish --tag="audit-chain-config"
php artisan vendor:publish --tag="audit-chain-migrations"
php artisan migrate
```

Usage
-----

[](#usage)

### Mode 1: Activity Log (Light)

[](#mode-1-activity-log-light)

Simple activity logging without hash chains. Audit logs have `hash` and `prev_hash` set to `null`.

```
use GrayMatter\AuditChain\Concerns\HasActivityLog;
use GrayMatter\AuditChain\Contracts\Auditable;

class Post extends Model implements Auditable
{
    use HasActivityLog;
}
```

### Mode 2: Audit Trail (Full)

[](#mode-2-audit-trail-full)

Cryptographic hash chain for immutable, verifiable audit logs.

```
use GrayMatter\AuditChain\Concerns\HasAuditTrail;
use GrayMatter\AuditChain\Contracts\Auditable;

class Licence extends Model implements Auditable
{
    use HasAuditTrail;
}
```

### Custom Events

[](#custom-events)

Record business-level events via the `audit()` method:

```
// Simple event
$order->audit('shipped');

// Event with old/new values
$order->audit('status_changed',
    oldValues: ['status' => 'pending'],
    newValues: ['status' => 'shipped'],
);
```

### Batch Grouping

[](#batch-grouping)

Group related operations under a single batch UUID:

```
use GrayMatter\AuditChain\Facades\AuditChain;

AuditChain::batch(function () {
    $order->audit('shipped');
    $order->update(['status' => 'shipped']);
    $inventory->update(['quantity' => $inventory->quantity - 1]);
});
// All 3 audit logs share the same batch_uuid
```

### Free-Form Context

[](#free-form-context)

Attach metadata to all subsequent audit logs:

```
AuditChain::context(['source' => 'csv_import', 'file' => 'users.csv']);

// All audit logs created after this will include this context
User::create([...]);
User::create([...]);
```

### Disable Logging

[](#disable-logging)

Suppress audit logging for seeds, imports, or maintenance:

```
AuditChain::withoutAudit(function () {
    // No audit logs created during this callback
    User::factory()->count(1000)->create();
});
```

### SoftDeletes

[](#softdeletes)

SoftDeletes is auto-detected. When present, `restored` and `forceDeleted` events are automatically captured in addition to standard CRUD events.

### PersonalData Attribute

[](#personaldata-attribute)

Annotate model properties as personal data using the PHP 8 attribute:

```
use GrayMatter\AuditChain\Attributes\PersonalData;

class User extends Model implements Auditable
{
    use HasAuditTrail;

    #[PersonalData(description: 'User email address')]
    public string $email;

    #[PersonalData]
    public string $name;
}
```

Or use the `$personalData` array:

```
class User extends Model implements Auditable
{
    use HasAuditTrail;

    protected array $personalData = ['email', 'name'];
}
```

### Field Control

[](#field-control)

Control which fields are audited:

```
class User extends Model implements Auditable
{
    use HasAuditTrail;

    // Only audit these fields
    protected array $auditInclude = ['name', 'email', 'role'];

    // Or exclude specific fields
    protected array $auditExclude = ['last_login_at'];
}
```

Fields in the model's `$hidden` array (passwords, tokens, etc.) are **automatically excluded** from audit values.

### Accessing Audit Logs

[](#accessing-audit-logs)

```
$user->auditLogs; // MorphMany relation
$user->auditLogs()->where('event', 'updated')->get();
```

GDPR
----

[](#gdpr)

### Data Export (Article 15)

[](#data-export-article-15)

```
$data = $user->exportPersonalData();
// ['email' => 'john@example.com', 'name' => 'John Doe']
```

### Full Subject Access (Article 15)

[](#full-subject-access-article-15)

Export personal data and the complete audit trail in one call:

```
$export = $user->exportFullSubjectData();
// [
//     'personal_data' => ['email' => 'john@example.com', 'name' => 'John Doe'],
//     'audit_trail' => [
//         ['id' => '...', 'event' => 'created', 'old_values' => [], 'new_values' => [...], ...],
//         ['id' => '...', 'event' => 'updated', ...],
//     ],
// ]
```

The audit trail includes event, old/new values, personal data accessed, IP address, user agent, batch UUID, context, and timestamps — but excludes internal chain fields (hash, prev\_hash).

### Anonymization (Article 17)

[](#anonymization-article-17)

```
$user->anonymize();
// email => '[ANONYMIZED]-42', name => '[ANONYMIZED]-42'
```

Uses `saveQuietly()` internally to avoid triggering audit events during anonymization. The model's primary key is appended to avoid UNIQUE constraint violations when anonymizing multiple records.

### Read Tracking

[](#read-tracking)

Opt-in via config to capture `retrieved` events (Article 15 / Article 33 — who accessed personal data):

```
// config/audit-chain.php
'events' => [
    'log_reads' => true, // WARNING: very verbose
],
```

Chain Verification
------------------

[](#chain-verification)

### Artisan Command

[](#artisan-command)

```
# Verify the entire chain
php artisan audit:verify

# Filter by model type
php artisan audit:verify --type="App\Models\User"

# Filter by model type and ID
php artisan audit:verify --type="App\Models\User" --id=42

# Verify and send notifications on failure
php artisan audit:verify --notify
```

### Log Pruning

[](#log-pruning)

```
# Delete logs older than 90 days (default)
php artisan audit:prune

# Custom retention period
php artisan audit:prune --days=365

# Prune only specific model type
php artisan audit:prune --type="App\Models\User"
```

### Automated Scheduling (Cron)

[](#automated-scheduling-cron)

Schedule verification and pruning in your `routes/console.php`:

```
// routes/console.php (Laravel 11+)
Schedule::command('audit:verify --notify')->hourly();
Schedule::command('audit:prune --days=90')->daily();
```

### Notifications

[](#notifications)

When `--notify` is used and verification fails, notifications are sent based on config:

```
// config/audit-chain.php
'notifications' => [
    'channels' => ['mail', 'webhook'],
    'mail_to' => [env('AUDIT_ALERT_EMAIL', '')],
    'webhooks' => [
        env('AUDIT_ALERT_WEBHOOK_1'),
        // Add more webhook URLs as needed
    ],
],
```

Webhook payloads are compatible with **Slack**, **Microsoft Teams**, **Discord**, and custom endpoints. Both `text` and `content` keys are included for cross-platform compatibility.

### Programmatic API

[](#programmatic-api)

```
use GrayMatter\AuditChain\Services\AuditChainService;

$result = app(AuditChainService::class)->verifyChain();
// ['valid' => true, 'checked' => 150, 'errors' => []]
```

Configuration
-------------

[](#configuration)

```
// config/audit-chain.php

return [
    'connection' => null,
    'table' => 'audit_logs',
    'drivers' => ['database'],
    'chain_seed' => env('AUDIT_CHAIN_SEED', 'genesis'),
    'queue' => [
        'enabled' => true,
        'connection' => null,
        'queue' => 'default',
    ],
    'events' => [
        'log_reads' => false,
    ],
    'anonymization' => [
        'replacement' => '[ANONYMIZED]',
    ],
    'retention' => [
        'days' => 90,
    ],
    'notifications' => [
        'channels' => ['mail'],
        'mail_to' => [env('AUDIT_ALERT_EMAIL', '')],
        'webhooks' => [],
    ],
];
```

KeyDefaultDescription`connection``null`DB connection for audit logs (separate recommended)`table``audit_logs`Table name`drivers``['database']`Storage drivers`chain_seed``env('AUDIT_CHAIN_SEED', 'genesis')`Secret seed for genesis hash`queue.enabled``true`Dispatch audit recording to queue`queue.connection``null`Queue connection`queue.queue``default`Queue name`events.log_reads``false`Capture `retrieved` events`anonymization.replacement``[ANONYMIZED]`GDPR anonymization replacement string`retention.days``90`Days to keep logs (`audit:prune`)`notifications.channels``['mail']`Notification channels: `mail`, `webhook``notifications.mail_to``[]`Email addresses for mail alerts`notifications.webhooks``[]`Webhook URLs (Slack, Teams, Discord, custom)Security
--------

[](#security)

### Chain Seed

[](#chain-seed)

Set `AUDIT_CHAIN_SEED` in your `.env` to a random, secret value. The seed is used to compute the genesis hash — the first link in the chain. A predictable genesis hash weakens tamper-evidence guarantees.

```
AUDIT_CHAIN_SEED=your-random-secret-value
```

### Database User Permissions

[](#database-user-permissions)

For maximum immutability, use a dedicated database user for the audit connection with **INSERT and SELECT only** — no UPDATE or DELETE. Eloquent guards prevent modification at the application layer, but DB-level restrictions ensure immutability even if the application is compromised.

### Timestamps

[](#timestamps)

All audit timestamps are stored in UTC via `now()->utc()->toDateTimeString()` to ensure consistent hash computation across time zones.

### Eloquent Immutability

[](#eloquent-immutability)

The `AuditLog` model throws `RuntimeException` on `updating` and `deleting` events, preventing modification through Eloquent. The `$fillable` whitelist further restricts which attributes can be set.

Testing
-------

[](#testing)

```
composer test
# or
./vendor/bin/pest
```

Full quality checks (Pint + Rector + PHPStan + Pest):

```
composer quality
```

Licensing
---------

[](#licensing)

This package is dual-licensed:

- **MIT License** — for open source and non-commercial use
- **Commercial License** — required for proprietary/commercial use

See [LICENSE](LICENSE) and [LICENSE\_COMMERCIAL](LICENSE_COMMERCIAL) for details.

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance75

Regular maintenance activity

Popularity7

Limited adoption so far

Community7

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

135d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/95159ad13115add4f2785edcff1d23900f7e3ca4e8a7aca9ca1fb38f9c3a9f1e?d=identicon)[matthieuLabaune](/maintainers/matthieuLabaune)

---

Top Contributors

[![matthieuLabaune](https://avatars.githubusercontent.com/u/59911744?v=4)](https://github.com/matthieuLabaune "matthieuLabaune (6 commits)")

---

Tags

laraveleloquentAuditimmutableaudit-trailactivity-loghash-chaingdprcompliancenis2

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/graymatter-laravel-audit-chain/health.svg)

```
[![Health](https://phpackages.com/badges/graymatter-laravel-audit-chain/health.svg)](https://phpackages.com/packages/graymatter-laravel-audit-chain)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.3M346](/packages/psalm-plugin-laravel)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.0k](/packages/simplestats-io-laravel-client)[spatie/laravel-health

Monitor the health of a Laravel application

87512.0M167](/packages/spatie-laravel-health)[laravel/ai

The official AI SDK for Laravel.

1.0k3.2M203](/packages/laravel-ai)[api-platform/laravel

API Platform support for Laravel

58171.8k14](/packages/api-platform-laravel)[clickbar/laravel-magellan

This package provides functionality for working with the postgis extension in Laravel.

440902.9k1](/packages/clickbar-laravel-magellan)

PHPackages © 2026

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