
August 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
When a staff member edits a client record, changes an order total, or deletes a document on a production Laravel application, you need more than updated timestamps—you need an audit trail. Laravel Activity Log with Spatie Package (spatie/laravel-activitylog) records model changes, custom business events, and the user who triggered them, stored in a dedicated database table you can query, filter, and display in an admin panel. On legal-tech portals and client portals I've maintained, that kind of accountability is not optional. This guide walks through installation, configuration, real-world patterns, and the production gotchas that bite teams after launch.
spatie/laravel-activitylog, run its migration, add the LogsActivity trait to Eloquent models, and configure getActivitylogOptions(). Laravel Activity Log with Spatie Package then auto-records attribute changes, causer, subject, and optional custom properties on every save or delete.What does Laravel Activity Log with Spatie Package actually record?
The package persists each event as a row in an activity_log table. Every row captures four conceptual pieces: what happened (description and event name), who did it (the causer—usually an authenticated user), what was affected (the subject—typically an Eloquent model), and context (changed attributes stored as JSON in properties).
That separation matters when you build compliance-friendly admin screens. A law-firm client portal might show "Advocate Sharma updated Case #1042 status from draft to submitted" without exposing unrelated model fields. The same structure works on eCommerce platforms where finance teams ask who changed a voucher balance.
Common columns you will work with:
description— human-readable text such as "updated" or a custom string you set.event— machine name likecreated,updated, ordeleted.subject_type/subject_id— polymorphic link to the affected model.causer_type/causer_id— polymorphic link to the acting user.properties— JSON withattributes,old, and any custom keys you add.batch_uuid— groups related logs from one request (useful for multi-model workflows).
The package integrates cleanly with Laravel authorization because logging happens at the model layer, after your policies and Form Requests have already validated input. It does not replace authorization—it documents what passed through it.
How do you install and configure the Spatie activity log package?
Target Laravel 13.x on PHP 8.3 or higher for new projects; Laravel 12 on PHP 8.2 still runs the same package with equivalent setup. Install with Composer 2.10:
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config" That creates config/activitylog.php and the migration for the activity_log table. The published config lets you set a default log name, enable or disable logging globally, and define how long you retain records.
Basic model setup
Add the trait and implement getActivitylogOptions() on any model you want audited:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
class Invoice extends Model
{
use LogsActivity;
protected $fillable = [
'client_id',
'amount',
'status',
'due_date',
];
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['amount', 'status', 'due_date'])
->logOnlyDirty()
->dontSubmitEmptyLogs()
->useLogName('billing');
}
} Three options deserve emphasis:
logOnly()— whitelist attributes. Never log passwords, API tokens, or encrypted payloads.logOnlyDirty()— skip rows where nothing meaningful changed (reduces noise).dontSubmitEmptyLogs()— suppress entries when dirty-check finds zero diffs.
On applications using Redis 8.10 for cache and queues, activity logging stays synchronous by default. That is fine for admin CRUD. For high-volume imports, queue the writes or batch them—covered later.
How do you log custom events beyond model saves?
Not every business action maps to an Eloquent update. Payment captured, document downloaded, case status manually overridden—these deserve explicit log entries. Spatie provides the activity() helper and the Activity facade.
use Spatie\Activitylog\Models\Activity;
activity('documents')
->causedBy(auth()->user())
->performedOn($document)
->withProperties([
'ip' => request()->ip(),
'action' => 'download',
'file_size' => $document->size,
])
->event('downloaded')
->log('Client downloaded notarized PDF'); Chain methods in any order before ->log(). The performedOn() call sets the subject; causedBy() sets the causer. Custom properties land in the JSON column and are searchable if you add indexes or export to reporting tools.
For manual logs inside service classes—patterns I use in enterprise Laravel applications—keep descriptions consistent so filters work:
- Use fixed log names:
billing,documents,auth. - Use fixed event strings:
downloaded,approved,archived. - Store machine-readable codes in
properties, human text indescription.
Which LogOptions settings should you use in production?
The LogOptions fluent API controls granularity. Pick settings deliberately—over-logging fills disks and slows admin queries; under-logging fails audits.
| Method | What it does | When to use it |
|---|---|---|
logAll() | Logs every attribute on create/update/delete | Small models with no secrets; rarely right for User models |
logFillable() | Logs only mass-assignable fields | Quick start; verify fillable excludes sensitive columns |
logOnly([...]) | Explicit attribute whitelist | Recommended default for production audit trails |
logExcept([...]) | Blacklist specific columns | When most fields are safe except a few |
logOnlyDirty() | Records only changed attributes | Almost always enable this |
dontLogIfAttributesChangedOnly([...]) | Ignores noise like updated_at | Models touched frequently by background jobs |
Customising entries with tapActivity
Override tapActivity() on the model when you need to enrich or redact before insert:
public function tapActivity(Activity $activity, string $eventName): void
{
$activity->description = "Invoice #{$this->id} {$eventName}";
$props = $activity->properties->toArray();
unset($props['attributes']['internal_notes']);
$activity->properties = collect($props);
} That pattern saved a client portal project from logging internal advocate notes into a client-visible audit export. Treat redaction as a first-class requirement, not an afterthought.
Disabling logging temporarily
Seeders, imports, and sync jobs should not pollute audit history:
use Spatie\Activitylog\Models\Activity;
Activity::disableLogging();
/* bulk update thousands of rows */
Activity::enableLogging(); Wrap imports in try/finally so logging re-enables even when an exception fires. I've debugged production tables bloated to millions of rows because a nightly sync forgot to disable logging.
How do you query, display, and retain activity logs?
The package ships an Activity Eloquent model. Query it like any other model, with relationships for causer and subject:
use Spatie\Activitylog\Models\Activity;
$logs = Activity::inLog('billing')
->causedBy($user)
->forSubject($invoice)
->latest()
->paginate(25); Scopes such as inLog(), causedBy(), and forSubject() keep controller code readable. For JSON property search on MySQL 9.7 or PostgreSQL 18, use database JSON operators sparingly—index hot paths or mirror critical fields into dedicated columns if reporting demands it. See PostgreSQL for Laravel developers for JSON indexing trade-offs on larger deployments.
Retention and cleanup
The package includes an Artisan command to prune old records:
php artisan activitylog:clean --days=365 Schedule it in routes/console.php or your scheduler. For a portal with 50 staff and moderate CRUD, a year of logs often fits comfortably on a modest VPS. High-traffic eCommerce systems may need 90-day retention plus cold storage export. Document your retention policy—auditors ask.
Batch UUID for multi-step workflows
When one user action touches several models—approving a booking that updates inventory, payment, and notification records—assign a shared batch UUID so the admin UI groups them:
use Illuminate\Support\Str;
use Spatie\Activitylog\Facades\LogBatch;
LogBatch::startBatch();
$batchUuid = Str::uuid();
/* multiple model saves and activity() calls */
LogBatch::endBatch(); Readers of the log see one logical operation instead of scattered rows. That mirrors patterns discussed in event sourcing with Laravel Spatie packages, though activity log is an audit trail, not a full event store.
What production mistakes break Spatie activity logging?
Most failures I encounter are environmental or architectural, not package bugs.
Logging sensitive data
Never pass passwords, reset tokens, card numbers, or full KYC payloads into logOnly() lists. If a column might contain PII, exclude it. GDPR and local data-protection expectations apply even for Nepal-based businesses serving international clients. When debugging, use JSON formatter tools on exported log samples in staging—not production—to verify redaction.
Missing causer on queued jobs
When a job mutates models, auth()->user() is null. Pass the initiating user ID into the job and call activity()->causedBy($user) or set causer on the model before save using CauserResolver binding. Otherwise every automated change appears as "system" with no accountability.
Table growth without indexes
On MySQL 8.4 LTS or MySQL 9.7, ensure indexes exist on columns you filter—at minimum log_name, created_at, and polymorphic causer/subject keys. Slow admin audit pages get disabled by frustrated staff, which defeats the purpose.
Confusing activity log with versioning
Activity log records that a change happened and stores before/after snapshots in JSON. It does not give you point-in-time model reconstruction across arbitrary history the way a dedicated versioning package or event sourcing would. Choose the right tool; see CQRS pattern in Laravel when it helps for heavier audit requirements.
Deploy through your normal pipeline—GitLab CI for Laravel with Deployer 7—and run migrations before symlink swap. The activity log table is append-heavy; plan backups accordingly alongside your main application database.
How does Spatie activity log compare to rolling your own?
Teams sometimes debate a custom audit_logs table versus a maintained package. Spatie wins on polymorphic causer/subject relationships, dirty detection, batch support, and battle-tested edge cases. A DIY table makes sense only when requirements are trivial—single model, no causer polymorphism, no JSON diffs.
Spatie sits alongside other packages I reach for regularly—Spatie Media Library for documents on portals like Mijar Law Associates, and Permission for RBAC. Together they form a practical admin foundation without bloating your codebase. Official docs live at Spatie's laravel-activitylog documentation; Laravel's Eloquent events are documented in the Laravel 13.x Eloquent guide.
For API-only apps exposing audit history, paginate aggressively and authorize with policies—never leak one client's logs to another. Patterns from Laravel API best practices and building RESTful APIs with Laravel apply directly.
Key Takeaways
- Install
spatie/laravel-activitylog, publish migrations and config, then addLogsActivitywith an explicitlogOnly()whitelist—notlogAll()on models that touch sensitive data. - Enable
logOnlyDirty()anddontSubmitEmptyLogs()on every production model to keep theactivity_logtable lean. - Use the
activity()helper for business events (downloads, approvals, logins) that never touch Eloquent attributes. - Schedule
activitylog:clean, index filter columns, and disable logging during bulk imports. - Pass the causer into queued jobs so automated changes remain attributable.
- Build the admin audit UI early—Laravel Activity Log with Spatie Package only helps if staff can actually read the trail.
People Also Ask
Does Spatie activity log work with Laravel 13?
Yes. Install the current major release of spatie/laravel-activitylog on Laravel 13.x with PHP 8.3 or higher. Laravel 12 applications on PHP 8.2 use the same trait and configuration API. Run composer update spatie/laravel-activitylog during framework upgrades and re-run migrations if the package publishes new columns.
Can you log activity without an authenticated user?
Yes. Omit causedBy() and the causer columns stay null, or pass a system user model instance representing automated processes. For accountability, create a dedicated "System" user record rather than leaving causer empty on routine automated jobs you still want traceable.
How is activity log different from Laravel's built-in model events?
Eloquent created, updated, and deleted events fire hooks inside your application lifecycle. Activity log persists a durable database row with JSON diffs, causer, subject, and timestamps suitable for admin audit screens and compliance exports. Events are for code reactions; activity log is for human-readable history.
Can you log related model changes on a parent record?
Not automatically across relationships. Log each model with its own trait, or use activity() on the parent after child saves, referencing both in withProperties(). Batch UUID grouping makes related entries easy to display together in one admin timeline.
Ship audit trails that survive the first compliance question
Laravel Activity Log with Spatie Package turns "who changed this?" from a database archaeology exercise into a query you run in seconds. Whitelist attributes, redact sensitive fields, schedule cleanup, and wire a simple admin view—before a client dispute or VAT audit makes it urgent. If you want activity logging integrated into a portal, booking system, or custom Laravel application with proper authorization and deployment, get in touch. For related Spatie tooling, read how to create custom Laravel packages, essential Laravel plugins, and modern Laravel architecture best practices—then inspect a shipped example on the Notary Nepal portfolio case study or explore ongoing Laravel support and maintenance if you already have an app that needs audit logging retrofitted.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

