bestora/laravel-filament-activity-log
A Filament plugin for browsing and managing activity logs—track model changes, user actions, and events in a dedicated panel resource with filtering, timelines, and easy integration into your Laravel admin.
spatie/laravel-activitylog is installed and migrated first — this package renders activity records, it does not create them:
composer require bestora/laravel-filament-activity-log
php artisan vendor:publish --tag="filament-activity-log-config"
php artisan vendor:publish --tag="filament-activity-log-translations"
->plugin(\Bestora\FilamentActivityLog\FilamentActivityLogPlugin::make())
config/filament-activity-log.php. The resources array is the control surface: which ActivityResource class is used, which columns are shown, and (as of v0.0.3) resources.hide_properties_column_by_default, which controls whether the "Changes" column is hidden in the table.use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Post extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()->logFillable()->logOnlyDirty();
}
}
Post in the panel, then open the activity resource, click into the record, and you'll see the causer, event, timestamp, and (v0.0.3) a linked subject plus a "Changes" section showing old → new values.Linking subjects to resources. v0.0.3 turns the subject column in the table and the subject field on the view page into links to the related record's edit page. The link is resolved via the activity's subject model's resource; if no resource (or no record) can be resolved, no link is rendered. This is opt-in by consequence: if your model has a Filament resource, you get links for free.
Mapping child models onto a parent resource. For models that don't have their own resource (pivot-ish models, child records, polymorphic sub-types), implement HasCustomActivityResource on the model and return the resource you want the subject to link to:
use Bestora\FilamentActivityLog\Contracts\HasCustomActivityResource;
class PostComment extends Model implements HasCustomActivityResource
{
public static function getActivityResource(): string
{
return \App\Filament\Resources\PostResource::class;
}
}
This is the supported extension point for keeping navigation coherent when one resource owns several auditable models.
Reading the diff. The view page renders a dedicated "Changes" section built by reusing the properties view. The section is hidden entirely when the activity has no diff (e.g. created events where old is empty, or activities logged without logOnlyDirty). Design your LogOptions accordingly — logOnlyDirty() plus dontSubmitEmptyLogs() gives you the cleanest diffs to display.
Column visibility policy. Because the "Changes" column is now visible by default, decide per panel whether that's desirable. Busy activity tables get wide fast; if you only care about the diff on the detail page:
// config/filament-activity-log.php
'resources' => [
'hide_properties_column_by_default' => true,
],
Users can still toggle the column via the table's column manager, so this is a default, not a lock.
Localisation. The changes key now ships in all bundled locales, so non-English panels no longer show a raw translation key for the new section. If you maintain custom translations, re-publish (or diff) the translations to pick up the new key; a missing changes key falls back to the raw key string rather than breaking the page.
Testing the link resolution. v0.0.3 adds tests asserting getResourceUrl() returns no link when the activity has no resolvable subject. Mirror that in your own feature tests: assert getResourceUrl() is null for orphaned activities (deleted subject, model without resource), and non-null for the happy path. It's the cheapest regression guard for custom HasCustomActivityResource implementations.
hide_properties_column_by_default to true if you want v0.0.2 behaviour.php artisan config:clear (or config:cache in deploy). A stale cached config is the usual cause of "I set the option but nothing changed".HasCustomActivityResource returning a resource class that actually exists, (3) does the record still exist, (4) is the current user authorized to view/edit that resource. Authorization failures look identical to "no resource configured".HasCustomActivityResource returning a resource for a different model than the subject is fine for navigation but confusing for users who land on a parent edit page. Prefer it only for genuinely child records (e.g. PostComment → PostResource).logFillable()/logOnly()), uses logOnlyDirty(), and that the activity isn't a created/deleted event with empty old.resources config array (swap in your own resource class), the HasCustomActivityResource contract (link mapping), and published translations (wording). Customising columns on the resource subclass is cheaper than overriding the whole table.activity_log table side (activitylog:clean) and keep the table's default sort on created_at descending — the diff section makes the detail page much heavier to read, and reviewers will thank you for not making them paginate 200 rows to find today's change.How can I help you explore Laravel packages today?