crescent-purchasing/filament-auditing
Filament Auditing adds a polished audit trail to Filament apps, powered by Laravel Auditing. Browse model changes, see who made them, and inspect old and new values in a searchable, filterable resource. Quick to install and configurable.
This is a Filament panel plugin that surfaces owen-it/laravel-auditing records as a browsable, filterable resource inside your admin panel. It does not record audits itself — that's the underlying package's job — so treat it as the UI layer over an audit trail you are already producing (or need to start producing).
Minimum path to a working screen:
composer require crescent-purchasing/filament-auditing — v2 requires Filament v4 or v5. If you are still on Filament v3, stay on the 1.x line.composer require owen-it/laravel-auditing, publish and run its migration so the audits table exists. Without that table the plugin's resource will 500 on first load.Auditable trait to one model, and be explicit about what you care about:
use OwenIt\Auditing\Contracts\Auditable;
class Invoice extends Model implements Auditable
{
use \OwenIt\Auditing\Auditable;
protected $auditEvents = ['created', 'updated', 'deleted'];
protected $auditInclude = ['status', 'total', 'assigned_to'];
}
app/Providers/Filament/AdminPanelProvider.php):
->plugin(\CrescentPurchasing\FilamentAuditing\FilamentAuditingPlugin::make())
Confirm the exact namespace against the package src/ or README — the class name follows the usual Filament plugin convention.Where to look first: after install, publish the package's views/config (php artisan vendor:publish) so you can see how the table is composed. The resource class is the thing you will actually be editing day to day — everything below assumes you are comfortable subclassing or overriding it rather than forking the package.
First real use case: point it at a single high-value model (payments, invoices, subscriptions — anything with money or compliance attached), turn auditing on for 3–5 fields only, and use the panel as a "who changed what, when" view during support triage. That is 80% of the value and it takes under an hour.
Global log vs. per-record context. Out of the box the plugin gives you one panel-wide "Audits" resource — great for investigations ("show me everything that happened between 14:00 and 15:00"), bad for "what happened to this invoice". The usual pattern is both: keep the global page for cross-cutting queries, and add a RelationManager to your domain resources that reuses the same table schema scoped to $this->getOwnerRecord()->audits(). The relation manager should be read-only — no create/edit actions.
Filters are the product. The table is only as useful as its filters. Expect to add or reorder: event type, auditable type/model, user, and a date range (defaulting to "last 7 days" so the first page load isn't a table scan). Users care far more about "show me deletions by this person this week" than about a raw chronological dump — save those as named filters once you see which ones people ask for.
Rendering the diff is your job, not the package's. old_values / new_values arrive as JSON. Raw json_encode in a column is unreadable in production. The pattern that works:
field: old → new lines.assigned_to → "Assignee"), falling back to Str::headline().Authorization. Filament policies do the heavy lifting — publish/implement a policy so only the right roles can view the audit resource at all. Be deliberate: audit logs frequently contain PII and pre-change values (old email addresses, previous salaries). If the resource is visible to everyone who can see the panel, you have effectively widened your data exposure.
Multi-tenancy. Audits are not tenant-scoped by the underlying package. In a multi-tenant panel, scope the resource query yourself — either via a modifyQueryUsing on the plugin/table or by joining on your tenant key — or you will leak other tenants' change history. This is the single most common production mistake with audit UIs.
Retention. Plan the pruning story on day one. audits grows monotonically and a busy tenant will hit millions of rows within a year. Use the auditing package's cleanup command on a schedule, keep the resource's default sort on created_at descending, and put an index on (auditable_type, auditable_id, created_at) before you need it.
Revert / restore. Since v2 consolidated the row actions into a single Filament v4-style Action, the natural extension point is one action that opens a details view and, when the audit represents a deleted event on a SoftDeletes model, offers a restore. Guard it hard: only offer restore for restorable events, wrap it in try/catch, log who performed the restore, and give it a confirmation modal with the diff shown.
ViewAction, restore-specific action classes, the Filament\Tables\Actions\* namespace) must be migrated to the unified Filament\Actions\Action API. Grep your codebase for the old namespace before you bump.$auditEvents defaults bite. Enabling retrieved (or restored) multiplies table size by an order of magnitude. Audit the events you'd actually investigate, and use $auditInclude to drop noisy columns — otherwise every updated_at touch writes a row.Relation::enforceMorphMap(), auditable_type starts storing aliases; existing rows keep FQCNs and your model-type filter silently splits into two buckets. Either map from the start or write a data migration.user_id is normal, not a bug. Queue workers, scheduled commands, and seeders produce audits with no user. Add a "system" filter option so those rows are explicable rather than confusing.old_values/new_values come back as arrays or strings depending on your Audit model config. If your diff renderer behaves differently in tests than in the panel, that cast is usually why.audits have rows for the record (Audit::where('auditable_id', $id)->latest()->first())? Is the trait on the model with the right $auditEvents? Is the plugin actually registered on this panel (php artisan route:list | grep audit)? Is the resource hidden by a policy? That sequence resolves most "nothing shows up" reports.How can I help you explore Laravel packages today?