alexjustesen/filament-spatie-laravel-activitylog
A Filament plugin that integrates spatie/laravel-activitylog, adding an activity log resource so you can browse, filter, and inspect model changes and user actions directly from your admin panel. Includes customizable tables, filters, and detail views out of the box.
This package is a thin Filament panel plugin that surfaces spatie/laravel-activitylog records as a read-only Filament resource. It does not log anything itself — it only reads the activity_log table. Plan for two installs, not one.
Install the logging engine first. The plugin assumes spatie/laravel-activitylog is already working:
composer require spatie/laravel-activitylog
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
Confirm the activity_log table exists before touching the plugin — an empty resource page is almost always a missing migration, not a broken plugin.
Install the plugin (composer require alexjustesen/filament-spatie-laravel-activitylog) and register it on the panel(s) in app/Providers/Filament/*PanelProvider.php:
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentSpatieLaravelActivitylogPlugin::make());
}
The plugin class name and any ::make()->… configuration methods are version-specific — copy the exact snippet from the readme shipped with the tag you install, not from a blog post.
Look here first, in this order: the package readme → the plugin class in vendor/alexjustesen/filament-spatie-laravel-activitylog/src/ (it is small; it shows exactly which resource gets registered and what is configurable) → vendor/.../Resources/ActivityResource.php for columns and filters → config/activitylog.php for the model swap and pruning settings.
First use case: make one model auditable and read it back through the panel.
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Invoice extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['status', 'total', 'client_id'])
->logOnlyDirty()
->dontSubmitEmptyLogs();
}
}
Edit an invoice in the panel, open the activity resource, and you should see the causer (the logged-in user), the subject link, and the attribute diff. If you see rows but no causer, the record was written from a console command or queued job — that is expected, not a bug.
Start narrow, then widen. Turn on LogsActivity for the two or three models where "who changed this and when" is a real support question (billing, permissions, publish state). Logging every model produces a table you cannot search and an activity page nobody opens.
Treat the Filament resource as a support tool, not an admin feature. The highest-value workflow is: customer reports a wrong value → find the subject record → the activity row gives you causer, timestamp, and the old/new attribute pair. Configure ->logOnlyDirty() so the properties payload is a compact diff rather than a full attribute dump; full dumps are noisy in the UI and store far more PII than you want.
Authorize it explicitly. The resource has no domain policy out of the box, so every panel user who can reach the panel can read every logged record — including properties containing emails, prices, and internal IDs. Gate it in one of two ways:
canViewAny() / canView() on your own resource class, orDo this before shipping, not after.
Custom Activity model and morph maps. If you extend Spatie\Activitylog\Models\Activity (adding scopes or accessors) and point config('activitylog.activity_model') at it, verify the plugin actually resolves through that config — older 0.x tags hard-wire the base model. If not, you'll need to publish/override the resource. The same applies to Relation::enforceMorphMap(): set it up before you generate activity, because subject_type stores whatever string was configured at write time. Introducing a morph map later leaves older rows with fully-qualified class names that no longer resolve, and the subject links silently degrade.
Multi-tenancy. The plugin does not scope by tenant. On a multi-tenant panel, either add a global scope keyed off the panel's tenant (via getEloquentQuery() on a subclassed resource) or restrict the plugin to a central/admin panel. Without this, tenant A reads tenant B's audit trail.
Retention is part of the implementation. activity_log grows monotonically and the resource's date/property filters do full-ish scans once the table is large. Schedule the built-in cleanup in routes/console.php or the scheduler:
$schedule->command('activitylog:clean')->daily();
and tune config('activitylog.delete_records_older_than_days') (default 365) to your actual compliance requirement.
Compose with your existing Filament resources by adding a relation manager or a header action that links to the filtered activity list for a record, rather than making users hunt through the global table.
alexjustesen/filament-spatie-laravel-activitylog:^0.6.3 and do not let Composer float you into 0.7.x. Pre-1.0 semver means a minor bump can absolutely break your panel; constrain it explicitly in composer.json.activity_log table migrated, at least one model using LogsActivity, and caches cleared — php artisan filament:optimize-clear (or optimize:clear), since cached Filament components hide newly registered resources.LogsActivity only fires on updated/created/deleted for models saved through Eloquent. Mass updates via query builder (Model::where(...)->update(...)) and raw SQL bypass it entirely — a common and silent gap.null. Expected for console commands, queue workers, and unauthenticated requests. If you need attribution there, use activity()->causedBy($user)->log(...) or Activity::saving() in a service provider.properties is empty or huge. Empty means logOnlyDirty() + nothing actually changed (dontSubmitEmptyLogs() hides those). Huge means you logged every attribute; move to logOnly([...]).logOnly. Add ->logExcept([...]) or a $logAttributesToSkip-style redaction before enabling the resource for non-developers.(subject_type, subject_id) and on created_at if you filter by date heavily; the package ships no indexes beyond Spatie's defaults.vendor/ patch to vanish on the next composer update.How can I help you explore Laravel packages today?