Appearance
Audit trail
What it does
Records who created, updated, published or deleted content, with before and after values. Site admins see their own site's entries. The super admin sees everything, including logins.
It is built on spatie/laravel-activitylog v5 (requires PHP 8.4+ and Laravel 12+). Entries are stored in the activity_log table in the app's normal database, not in S3.
How it works
What the package does by itself: listens to created, updated and deleted events on models that use its trait, records the user who did it (the causer) and the record (the subject), stores before and after values in attribute_changes, and provides activitylog:clean.
What was added for this platform:
site_idcolumn onactivity_log(nullable, indexed), plus a customApp\Models\Activitywith asite()relationship, set inconfig/activitylog.phpasactivity_model. This is what lets Filament scope the audit screen to a tenant.LogsSiteActivitytrait (app/Models/Concerns/). It wraps the package's trait with shared defaults: log all attributes exceptcreated_at,updated_at,passwordandremember_token, only log what actually changed, and skip empty changes. ItsbeforeActivityLogged()hook:- sets
site_idfromactivitySiteId(), falling back to the current Filament tenant - stores
subject_label(the record's title or name) inproperties, so entries stay readable after the record is deleted - renames an
updatedevent topublishedorunpublishedwhenstatuschanges
- sets
- Per-model overrides:
PageBlocktakes its site from its page, ignoressort_orderand is labelled "Page title > key".Siteis its own site.Brandinghas a fixed label.Useralso excludes two-factor secrets. - Global hook in
AppServiceProvider::boot()(Activity::beforeLogging) addscauser_name,ipanduser_agenttopropertieson every entry. - Login, logout and failed login are not model changes, so
LogAuthenticationEventsrecords them by hand under theauthlog name. Failed logins record the email only, never the password. These have no site, so they appear only in/system. - Screens:
AuditLogResourcein the tenant panel (scoped to the current site) and a copy under/systemthat shows all sites. Both shareAuditLogsTable(with ashowSiteflag) andAuditLogInfolist. - Access:
ActivityPolicyrequiresaudit.view(site-admin only) and checks the entry'ssite_idagainst the tenant. Create, edit and delete are disabled. - Retention:
activitylog:clean --forceis scheduled daily inroutes/console.php, deleting entries older thanclean_after_days(365).
Turning it on for a model
Add the trait. Nothing in the Filament resources or forms changes.
php
use App\Models\Concerns\LogsSiteActivity;
class Page extends Model
{
use LogsSiteActivity;
}Models without the trait are not logged. Override activityExclude(), activitySiteId() or activityLabel() when a model needs different behaviour.
Currently applied to: Page, PageBlock, Package, Post, Media, Branding, Site, User (check each model has the trait).
Files
config/activitylog.php- Migrations: the package's
activity_logmigration, plusadd_site_id_to_activity_log_table app/Models/Activity.php,app/Models/Concerns/LogsSiteActivity.phpapp/Listeners/LogAuthenticationEvents.phpapp/Providers/AppServiceProvider.php(the global hook)app/Policies/ActivityPolicy.phpapp/Filament/Resources/AuditLogs/andapp/Filament/System/Resources/AuditLogs/routes/console.php
Why it's built this way
- The package, with a custom model, instead of a hand-written logger. Tenant scoping, the only gap, is handled by one column and one relationship.
- A shared trait so each model needs one line and the rules live in one place.
- Snapshots in
properties(causer_name,subject_label) because relationships return null once a user or record is deleted. - One table and one set of screen classes shared by the site panel and
/system, so they can't drift apart.
Notes
- Only Eloquent model events are logged. Raw
where()->update()queries and database cascades are not. Deleting a page is logged, but its blocks cascade silently. - Role and site-assignment changes are not logged (see roles and permissions).