Search by

rembon / laravel-auditor

dayCodrembon

Request-level audit trail for Laravel: who did what, what changed, and what they were allowed to do.

Package info

github.com/Rembonnn/laravel-auditor

pkg:composer/rembon/laravel-auditor

Statistics

Installs: 133

Dependents: 0

Suggesters: 0

Stars: 3

Open Issues: 6

v3.0.0-beta.1 2026-09-30 06:10 UTC

This package is auto-updated.

Last update: 2026-10-07 12:06:12 UTC


README

tests coverage Packagist Downloads PHP 8.3+ Laravel 12–13

Request-level audit trail for Laravel — who did what, what changed, and what they were allowed to do. Production-safe and compliance-ready.

Laravel Auditor dashboard overview
More screenshots
Entries Entry detail with diff Mobile
Entries list Entry detail showing a model diff Entries on a phone

Why Laravel Auditor?

Every HTTP request, queued job and artisan command becomes one entry: the user, the route, the status, the abilities that were checked (granted or denied), the records that were read, the mails and notifications that went out — and a diff of every model it changed.

Laravel Auditor v3 spatie/laravel-activitylog owen-it/laravel-auditing Laravel Telescope
Unit of recording Request / job / command Manual event / model Model event Debug entry
Model change diff ✅ ✅ ✅ ⚠️ (debug)
Models read (read audit) ✅ ❌ ⚠️ (retrieved) ⚠️
Abilities checked (granted/denied) ✅ ❌ ❌ ✅ (debug)
Mail & notifications sent ✅ (no body) ❌ ❌ ✅ (debug)
Correlation request → queued job ✅ ❌ ❌ ⚠️ (batch id)
Tamper-evident (hash chain) ✅ ❌ ❌ ❌
Built for production ✅ ✅ ✅ ⚠️ (not recommended)

Installation

composer require rembon/laravel-auditor
php artisan auditor:install

auditor:install publishes the config and migrations, offers to migrate, and prints the snippets below. Add --integrity to enable the hash chain.

Quick start

Record the changes of a model:

use Rembon\LaravelAuditor\Traits\Auditable;

class Post extends Model
{
    use Auditable;

    // Optional
    protected array $auditExclude = ['view_count'];
    protected array $auditEvents = ['updated', 'deleted'];
    protected bool $auditRetrieved = false;
}

Allow access to the dashboard outside local (it is closed by default):

// AppServiceProvider::boot()
Gate::define('viewAuditor', fn (User $user) => $user->is_admin);

Schedule the maintenance commands:

// routes/console.php
Schedule::command('auditor:prune')->daily();
Schedule::command('auditor:seal')->everyMinute()->withoutOverlapping(); // with integrity
Schedule::command('auditor:verify')->daily();                          // with integrity

Then open /auditor.

Usage

use Rembon\LaravelAuditor\Facades\Auditor;

// Enrich the current entry
Auditor::withProperty('order_id', $order->id);
Auditor::withProperties(['channel' => 'mobile']);
Auditor::tag('checkout', 'payment');

// Control recording
Auditor::ignore();                                 // drop this entry (model changes are kept)
Auditor::withoutAuditing(fn () => $post->save());  // nothing inside is recorded
Auditor::correlationId();                          // shared with the jobs it dispatches

// Customise (in a service provider)
Auditor::auth(fn (Request $request) => $request->user()?->isAdmin());
Auditor::resolveUserUsing(fn () => auth('admin')->user());
Auditor::filter(fn (EntryData $entry) => $entry->name !== 'health.check');
Auditor::redactUsing(fn (string $key, mixed $value) => $key === 'nik');
Auditor::extend('s3', fn (Application $app) => new S3Storage(/* ... */));
Auditor::displayUserUsing(fn ($user) => ['name' => $user->name, 'avatar' => $user->avatar_url]);

Query the trail:

$post->audits;                                              // newest first
ModelChange::query()->causedBy($user)->since(now()->subWeek())->get();
Entry::query()->forCorrelation($id)->get();                 // request + its jobs
Entry::query()->withDeniedAbilities()->get();

Testing your application

Auditor::fake();

$this->actingAs($user)->put("/posts/{$post->id}", ['title' => 'New']);

Auditor::assertChangeRecorded(Post::class, $post->id, ChangeEvent::Updated,
    fn (ModelChangeData $change) => $change->newValues['title'] === 'New');
Auditor::assertEntryRecorded(fn (EntryData $e) => $e->userId === (string) $user->id);
Auditor::assertAbilityDenied('delete-post');
Auditor::assertNothingRecorded();

Privacy & safety defaults

  • Keys such as password, *token*, *secret*, authorization, card numbers, $hidden attributes and encrypted casts are replaced with [REDACTED] — in model diffs, input, properties and URL query strings.
  • Mail bodies are never stored; recipients can be hashed (mail.hash_recipients).
  • IPs can be anonymised (http.capture.anonymize_ip), request input is off by default.
  • Recording never breaks your app: failures are reported, not thrown (AUDITOR_THROW=true in tests).
  • Entries are written after the response is sent, or through the queue (AUDITOR_QUEUE).
  • Integrity: rows are chained with HMAC-SHA256; auditor:verify detects modified and removed rows. It cannot protect against someone who holds both the key and the database.

Limitations

Mass updates/deletes through the query builder, insert(), and pivot attach()/detach() without a custom pivot model do not fire Eloquent events, so they are not recorded as model changes.

Ecosystem

Documentation

Development

composer test            # unit + feature
composer test:browser    # dashboard in a real browser (needs `npx playwright install chromium`)
composer analyse         # Larastan
composer serve           # workbench with demo data at http://localhost:8000/auditor

See CONTRIBUTING.md. Report vulnerabilities privately — see SECURITY.md.

Credits

License

MIT. See LICENSE.