always-open/laravel-model-auditlog
Laravel Model AuditLog automatically tracks changes to your Eloquent models, recording creates, updates, and deletes with the data needed for a clear audit trail. Add lightweight, configurable history to models without cluttering your app logic.
Install the package with Composer:
composer require always-open/laravel-model-auditlog
Publish the config and migration files:
php artisan vendor:publish --provider="AlwaysOpen\ModelAuditLog\AuditLogServiceProvider"
Check the published config/auditlog.php first — this is where you configure the audit model, user resolver, and attribute filtering.
Run the migrations to create the audit log table:
php artisan migrate
Add the package’s auditable trait to any Eloquent model you want to track:
use AlwaysOpen\ModelAuditLog\Traits\Auditable;
class Post extends Model
{
use Auditable;
}
First use case: create or update a record and inspect its audit history:
$post = Post::create(['title' => 'Hello']);
$post->update(['title' => 'Hello, world']);
$post->audits; // collection of audit entries
Confirm that old_values, new_values, event, user_id, and timestamps are populated as expected.
Upgrading from v9: v10.0.0 is primarily a Laravel 13 compatibility release. Before upgrading, verify your PHP version meets Laravel 13’s requirements, run the full test suite, and review the package’s upgrade guide for any trait or config signature changes. If you are not yet on Laravel 13, stay on the latest v9.x release.
Trait-based auditing for standard Eloquent models
Apply the Auditable trait to models that need automatic create/update/delete tracking. This is the lowest-effort path and covers the majority of day-to-day use cases.
class Invoice extends Model
{
use Auditable;
}
Custom audit models and relationships
If you need extra fields, custom casts, or a different table, extend or configure the package’s audit model in config/auditlog.php. Keep a dedicated AuditLog model so you can add scopes, accessors, and relationships without touching vendor code.
// config/auditlog.php
'model' => App\Models\AuditLog::class,
Attribute filtering and redaction
Use the config file or model-level overrides to exclude noisy or sensitive attributes. Typical exclusions include updated_at, password hashes, API tokens, and large JSON blobs.
protected $auditExclude = [
'password',
'remember_token',
'updated_at',
];
This is important for both security and table size.
User attribution
Configure the resolver that identifies the acting user. In queued jobs, console commands, and tests there is often no authenticated user, so make the resolver nullable and provide a fallback such as a system user ID or null.
'user' => [
'resolver' => function () {
return auth()->id();
},
],
Manual auditing for non-Eloquent events
For actions that do not map cleanly to model events — imports, bulk operations, external API syncs — use the package’s manual audit API if available, or dispatch an audit entry directly through the audit model. Wrap bulk writes in a transaction and create one audit entry per affected record where traceability matters.
Integration with Laravel events and queues
Audit logging is synchronous by default because it hooks into Eloquent events. If audit writes become a performance bottleneck, consider listening to the package’s audit-created event and offloading downstream processing (notifications, search indexing) to a queue rather than moving the audit write itself.
Testing
Use database transactions or RefreshDatabase in tests. Assert on audit entries directly:
$this->assertDatabaseHas('audit_logs', [
'auditable_type' => Post::class,
'auditable_id' => $post->id,
'event' => 'updated',
]);
For models with sensitive attributes, add a regression test that asserts excluded fields never appear in old_values or new_values.
Major version upgrade discipline
v10.0.0 is a major release. Even though the release note only calls out Laravel 13 support, major bumps can include dropped PHP versions, changed trait namespaces, or config key renames. Read the full changelog between v9.5.0 and v10.0.0, run composer why-not if the upgrade is blocked, and test on a staging branch before production.
Laravel 13 compatibility ripple effects
If you upgrade to Laravel 13, check your other packages first. An audit log package is rarely the only dependency that needs a framework major. Use composer update --with-all-dependencies cautiously and review the lock file diff.
No authenticated user in background contexts
Queued jobs, scheduled commands, and tests will often resolve auth()->id() to null. Decide whether that is acceptable or whether you need a system actor. Do not assume every audit row has a user.
Sensitive data leakage
Audit logs are often retained far longer than the source records and may be exported to data warehouses. Exclude passwords, tokens, PII, and encrypted fields explicitly. Do not rely on model $hidden — that affects serialization, not audit capture.
Soft deletes and force deletes
Auditing deleted, restored, and forceDeleted events requires the model to use SoftDeletes and the package to be configured to capture those events. Verify the behavior in tests; a hard delete can otherwise leave you with an audit trail that is harder to interpret.
Transaction boundaries
Audit entries are written inside the same database transaction as the model change in most configurations. If the transaction rolls back, the audit entry disappears too. That is usually correct, but it means audit logs are not a substitute for external, append-only compliance storage.
Large attribute payloads
Storing full old_values and new_values JSON for wide models or large text columns can bloat the audit table quickly. Exclude large fields, store only changed attributes if the package supports it, and add indexes on auditable_type, auditable_id, and created_at.
Polymorphic relation integrity
Audit rows reference models polymorphically. If a model is deleted or its class is renamed, historical audit rows can become orphaned or unreadable. Avoid renaming audited model classes without a migration strategy, and consider storing a stable morph alias.
Config caching
After changing config/auditlog.php in production, run php artisan config:cache. A stale config cache is a common reason why exclusions or user resolvers appear to be ignored.
Extension points
Prefer extending the audit model and overriding config values over modifying vendor files. If you need custom behavior on every audit write, look for the package’s events or a publishable audit model before reaching for a fork.
How can I help you explore Laravel packages today?