asignua/filament-activity-log-plus
Filament Activity Log Plus adds a polished activity log viewer to Filament admin panels, built on spatie/laravel-activitylog. Browse, filter, and inspect model changes with detailed diffs, timelines, and user attribution—ideal for auditing and tracking record history.
Require the package and run the install:
composer require <vendor>/activity-log-plus
php artisan vendor:publish --tag="activity-log-plus-config"
php artisan vendor:publish --tag="activity-log-plus-migrations"
php artisan migrate
Read these three things first, in this order:
config/activity-log-plus.php — the except list, batch settings and the UI options.config/activitylog.php → default_except_attributes — the fallback exclusion list used when a model has no explicit $activitylogExcept.batch_root and indexes batch_uuid; check whether your activity_log table already has that column (see the legacy note below).Wire the Filament plugin into a panel:
->plugin(ActivityLogPlusPlugin::make())
Link the compiled stylesheet after the panel theme (Filament's asset registration already does this for you; only hand-roll it if you're loading assets manually).
First real use case: put LogsActivityPlus on one noisy model, open the panel page, and confirm you see the batch grouping and the "From / Until" filter behaving as expected before rolling it out to every model.
If you are upgrading an existing activitylog v4 table, run the documented backfill once after migrating:
UPDATE activity_log SET batch_root = 1;
Without it, pre-existing rows have batch_root = 0 and will not group correctly.
Model-level logging with secrets excluded by default. LogsActivityPlus now skips $hidden attributes, any column with an encrypted / encrypted:* cast, and the AsEncryptedCollection / AsEncryptedArrayObject class casts. That covers Filament MFA (app_authentication_secret, recovery codes), two_factor_* and API tokens. Rely on the default except config rather than maintaining your own list, and only add to it:
protected array $activitylogExcept = ['api_token', 'internal_notes'];
Batch correlation. Every entry belongs to a batch with a batch_root. Day-to-day this means one panel row per operation with a "+N more" count — note that the count is of other records of the operation, not of entries, so a batch that touched 3 rows and wrote 9 entries shows "+2 more". Use batches for anything transactional (an import job, a multi-model save in a controller) and the panel becomes a request log rather than an entry log.
Permission auditing across spatie v6 and v7. Role grants and revocations are logged on both. v6 fires RoleAttached / RoleDetached; v7 renamed them to RoleAttachedEvent / RoleDetachedEvent. If you write your own listeners or assert on event classes in tests, branch on the installed version or register listeners by both names.
Request vs non-request context. ActivityBatch::start(http: true) marks a real HTTP request (Octane-safe). Entries written by artisan commands or queue workers no longer get the synthetic 127.0.0.1 address, which makes your "who did what" reports trustworthy. If you want queue work attributed, propagate the causal agent explicitly:
ActivityBatch::start(http: false, causer: $user);
Date filtering for reporting. "From / Until" now takes calendar days in the panel timezone and compares raw created_at (index-friendly) rather than whereDate() on UTC days. Any scheduled report you build that queries the same table should copy this approach — convert the boundary to UTC and compare raw, otherwise your counts will drift from the UI by up to a day.
Pivot titles. Related-model names for pivot entries are resolved without the global scopes of the related model, so soft-deleted or tenant-scoped records still show their name instead of #id. This means historical entries keep readable names after the record is deleted — but also that a name resolution does not respect your tenant scope, so don't use it to prove existence of a related record.
Existing rows are still leaky. The new exclusion is forward-only. Anything already written (MFA secrets, tokens in plaintext) is in the table. Audit and redact before you expose the panel to a wider audience:
SELECT id, properties FROM activity_log
WHERE JSON_EXTRACT(properties, '$.attributes.app_authentication_secret') IS NOT NULL;
Config cache. After touching except or default_except_attributes, run php artisan config:clear. A stale cached config is the most common reason "it still logged the secret".
Cast detection is string/class based. Custom casts that wrap their own encryption won't be picked up — mask them manually via $activitylogExcept or a tapActivity() hook.
Stylesheet scoping. Compiled CSS is now scoped to :where(.activity-log-plus) .flex, so it can't override your host's dark: and responsive utilities. If you forked the plugin's views or CSS, rebuild with npm run build, and make sure your markup still sits inside an .activity-log-plus container or the scoped rules won't apply.
Migration down() is destructive on shared tables. It drops batch_uuid even if the column pre-existed (activitylog v4). Never run migrate:rollback for this package on a production table without a backup.
whereDate() is gone from filters. Don't copy old snippets from the README of an earlier version into custom widgets; the semantics changed from UTC days to panel-timezone days.
Batch root edge case. A host update to the user during login/logout (last_login_at) no longer hijacks the batch root from the login / logout entry. If you were asserting on that in tests, expect different batch_root values.
IP attribution. Seeing 127.0.0.1 on artisan/queue entries was a bug, not a signal — if you keyed dashboards off it, switch to ActivityBatch::start(http: true) presence instead.
Extension points worth knowing: tapActivity() on the model trait, custom ActivityBatch starts, and the published panel views (re-publish after upgrades to pick up the scoped markup).
How can I help you explore Laravel packages today?