digit7s/filament-audit-toolkit
A Filament audit toolkit for Laravel apps that captures model and user activity, displays changes and history in a clean panel UI, and helps teams quickly review who changed what, when, and from where. Includes configurable audit trails, filters, and a searchable timeline for fast investigation.
This is a patch release (v0.1.1) on a pre-1.0 package, so treat the API as stabilizing rather than stable. There are no breaking changes, but there are behavior changes in query construction that are worth verifying before you assume your existing code is unaffected.
Start here:
composer require <vendor>/<package>, then publish config and migrations (php artisan vendor:publish --tag=<package>-config and -migrations) and run php artisan migrate. Confirm the audit table exists and is indexed on occurred_at (this package orders by it heavily; an unindexed occurred_at is the first thing that will bite you at volume).occurred_at values, event names, and categories are shaped as the package expects.If your app is MySQL-only and you had not yet exercised the Event or Category filters, this is the release that makes those filters correct. Do that smoke test before anything else.
Filter + ordering is the fragile combination. The two fixed bugs share a root cause: a DISTINCT filter query inherited an occurred_at ordering it was not designed to carry. In MySQL, SELECT DISTINCT ... ORDER BY occurred_at is only legal when occurred_at is in the select list, and even when legal it can change which row survives deduplication. Pattern to follow: when you build custom filter queries against the audit table, decide explicitly whether you are in "distinct set of values" mode or "chronological records" mode, and do not mix them. Distinct-value queries should not inherit the Explorer's default ordering.
Audit Explorer ordering. The Explorer is now guaranteed chronological. If you paginate it, add a deterministic tiebreaker (occurred_at, then id) — same-second events are common in tests and in bulk operations, and without a tiebreak your pagination will show duplicates and drop rows across page boundaries even though the base ordering is correct.
Authorization at the query builder, not the controller. The regression suite covers authorization boundaries, which suggests the intended pattern is that the gate is applied to the underlying query. Apply your policy as a scope before the Explorer's filters run ($query->visibleTo($user) then the package's filter methods), not as a post-hoc ->filter() on a collection — filtering after hydration will leak row counts through pagination totals.
Cross-database integration. The package now ships regression coverage for both SQLite and MySQL. Mirror that in your own app tests: run the Explorer feature tests on SQLite for speed in CI, but keep at least one MySQL-backed test for anything involving DISTINCT, GROUP BY, or ordering, because that is precisely the class of bug SQLite will not catch for you.
Event and Category as first-class filters. With these fixed, the natural pattern is to drive Explorer UI dropdowns from the same distinct-value queries the package exposes rather than hand-rolling pluck() calls against the audit table. You get the empty-table behavior fixes for free and stay consistent with the package's notion of an event name.
orderByRaw, or a ->groupBy() on top of the Explorer query, remove it now. Layering a manual groupBy onto the corrected query is the fastest way to reintroduce the old behavior on MySQL.DISTINCT + ORDER BY strictness differs by engine. SQLite tolerates ordering by a column absent from the select list under DISTINCT; MySQL (especially with ONLY_FULL_GROUP_BY) does not. Do not conclude the fix is broken because your SQLite dev environment and your MySQL staging environment disagree — check the generated SQL with ->toSql() and the bindings, not just the result set.ORDER BY clause or in a missing tiebreaker, and dd($results->pluck('occurred_at')) will look correct while pagination is still broken.composer update should be uneventful — but bump your lockfile deliberately and re-run your Explorer tests, since the changed behavior is in read paths that feature tests often do not cover.How can I help you explore Laravel packages today?