Request-level audit trail for Laravel: who did what, what changed, and what they were allowed to do.
rembon/laravel-auditor is a Laravel package for request-level audit trail for laravel: who did what, what changed, and what they were allowed to do..
It currently has 3 GitHub stars and 107 downloads on Packagist (latest version v3.0.0-beta.1).
Install it with composer require rembon/laravel-auditor.
Discover more Laravel packages by rembon
or browse all Laravel packages to compare alternatives.
Last updated
Request-level audit trail for Laravel — who did what, what changed, and what they were allowed to do. Production-safe and compliance-ready.
| Entries | Entry detail with diff | Mobile |
|---|---|---|
| 

|
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) |
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.
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.
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();
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();
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.hash_recipients).http.capture.anonymize_ip), request input is off by default.AUDITOR_THROW=true in tests).AUDITOR_QUEUE).auditor:verify detects modified and
removed rows. It cannot protect against someone who holds both the key and the database.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.
AuditsRelationManager for any resource, and a stats widget.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.
MIT. See LICENSE.