Skip to content

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_id column on activity_log (nullable, indexed), plus a custom App\Models\Activity with a site() relationship, set in config/activitylog.php as activity_model. This is what lets Filament scope the audit screen to a tenant.
  • LogsSiteActivity trait (app/Models/Concerns/). It wraps the package's trait with shared defaults: log all attributes except created_at, updated_at, password and remember_token, only log what actually changed, and skip empty changes. Its beforeActivityLogged() hook:
    • sets site_id from activitySiteId(), falling back to the current Filament tenant
    • stores subject_label (the record's title or name) in properties, so entries stay readable after the record is deleted
    • renames an updated event to published or unpublished when status changes
  • Per-model overrides: PageBlock takes its site from its page, ignores sort_order and is labelled "Page title > key". Site is its own site. Branding has a fixed label. User also excludes two-factor secrets.
  • Global hook in AppServiceProvider::boot() (Activity::beforeLogging) adds causer_name, ip and user_agent to properties on every entry.
  • Login, logout and failed login are not model changes, so LogAuthenticationEvents records them by hand under the auth log name. Failed logins record the email only, never the password. These have no site, so they appear only in /system.
  • Screens: AuditLogResource in the tenant panel (scoped to the current site) and a copy under /system that shows all sites. Both share AuditLogsTable (with a showSite flag) and AuditLogInfolist.
  • Access: ActivityPolicy requires audit.view (site-admin only) and checks the entry's site_id against the tenant. Create, edit and delete are disabled.
  • Retention: activitylog:clean --force is scheduled daily in routes/console.php, deleting entries older than clean_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_log migration, plus add_site_id_to_activity_log_table
  • app/Models/Activity.php, app/Models/Concerns/LogsSiteActivity.php
  • app/Listeners/LogAuthenticationEvents.php
  • app/Providers/AppServiceProvider.php (the global hook)
  • app/Policies/ActivityPolicy.php
  • app/Filament/Resources/AuditLogs/ and app/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).