nsd7/laravel-activitylog-ui
nsd7/laravel-activitylog-ui adds a clean, ready-to-use web interface for viewing Laravel activity logs. Browse, search, filter, and inspect logged events and changes without building your own admin screens. Ideal for Spatie laravel-activitylog users.
1. Confirm you actually have activity data first. This package is a read-only presentation layer over spatie/laravel-activitylog's activity_log table. If LogsActivity is not implemented on the models you care about, the dashboard renders an empty shell and you have silently started an instrumentation project instead. Before installing, sanity-check:
php artisan tinker
>>> Spatie\Activitylog\Models\Activity::count();
If that returns zero, stop and instrument first. If it returns data, note the spread of log_name, event, subject_type, and causer_type values — that determines whether the filters will be useful on day one.
2. Install and let auto-discovery do its job.
composer require nsd7/laravel-activitylog-ui
The service provider, route group, and precompiled Tailwind assets are registered automatically. There is no npm install and no build step — this is the package's main architectural selling point and the reason it drops cleanly into Forge/shared-hosting/lean-container deploys.
3. Publish and read the config before touching the browser. Use the publish tags listed in the configuration guide (tags are the authoritative reference; don't guess them). The config file is where you answer the questions that actually matter operationally: route prefix, route middleware, authorization gate, default pagination, and database connection. Get these right before you first load the dashboard, because the default state is the thing you should be verifying, not trusting.
4. Visit the dashboard, then immediately harden it. Load the configured route as a normal authenticated user who is not an admin and confirm you get a 403/404. If you get content, the route is live without a gate and you own that fix before anything else.
5. First real use case — the support ticket. Open a record you know changed, filter to its subject_type/subject_id, and read the before/after diff. That single workflow ("why did this record change, and who did it?") is your fastest demonstration of value. Time-to-answer on that question before and after install is the metric worth tracking.
Where to look first, in order: config file → authorization guide → dashboard filters → the JSON API guide. The API is a second entry point to the same sensitive data and needs its own decision, not a footnote.
Mount it behind your existing admin boundary, not beside it. Put the package's route group inside the middleware stack your admin area already uses (web, auth, plus whatever role/permission middleware you run), and additionally register a dedicated gate — something like viewAuditLog — rather than relying on is_admin checks scattered elsewhere. A named ability is what lets you later grant auditors read-only access without inventing a second admin tier.
// AppServiceProvider::boot()
Gate::define('viewAuditLog', fn (User $u) => $u->hasRole('admin') || $u->hasRole('auditor'));
Then reference that gate from the package's route middleware config. Treat it as required configuration, not optional hardening.
Instrumentation determines UI quality — invest in it deliberately. The dashboard can only render what Spatie records. Patterns that pay off:
logOnly([...]) or an explicit attribute list rather than logAll() on models with noisy or secret columns. Every field you log becomes a field visible in a diff, an export, and the JSON API.logName() (or useLogName) to partition logs by domain — billing, auth, content. These become the cleanest filter dimension in the UI and make saved views far more useful than filtering by class name.event values (created, updated, deleted, plus custom ones like approved, refunded). The event filter is one of the highest-leverage controls.Make causers and subjects human-readable. Model-aware views resolve causer/subject into labels — often via name or email. If your User model exposes only full_name, verify the viewer resolves it; if not, add an accessor rather than patching the views. Do the same for subjects: a bare App\Models\Invoice#4417 is far less useful than INV-4417 — Acme Ltd.
Deep-link from your own record pages. The highest-value integration is not the dashboard itself — it is a "History" link on each model's detail page that jumps into the viewer pre-filtered to that subject. That converts a separate tool into part of the workflow people already have open. Combine with a saved view per entity type so the filter set is one click.
Saved views for recurring investigations. Define a small, opinionated set — "Payment changes (last 24h)", "Role/permission changes", "Deleted records this week" — and treat them as team documentation. The value is that a support engineer does not have to reconstruct a filter query at 2am.
Timeline for incident forensics. The timeline is the right tool when you have a when but not a what: pin a window around an incident and a causer, then read the sequence. Analytics charts answer "how much activity, from whom, over time"; the timeline answers "what happened, in order".
Exports: decide the policy before enabling them. CSV for spreadsheet reconciliation, JSON for programmatic handoff, Excel/PDF for auditor deliverables. Two rules: (a) confirm the export dependencies (dompdf, maatwebsite/excel) are suggest in composer.json and only install what you actually need — you do not want a PDF engine pulled into production as a side effect of a UI package; (b) define retention and PII scope for exports before the first export leaves the building, because an export is a copy that leaves your access controls behind.
Multi-database support is the differentiator for sharded/split-tenant estates. If you run per-tenant connections, configure the connection per request (or per mounting) rather than assuming a single global connection. A single-connection viewer is a hard blocker in those setups, so this is the feature that decides adopt-vs-skip for that architecture.
Localization is documented — publish the translation files and add locales if your internal audience is non-English. Do this at rollout, not later; retrofitting translated audit vocabulary into saved views and exported headers is annoying.
Customization path. The views are publishable Blade + Tailwind. For light branding (headings, logo, colors), publish and edit. If you find yourself rewriting more than a couple of views, stop and wrap instead: keep the package's views intact and override at the layout/routing layer, so upgrading stays cheap. Patching vendored views deeply is how a two-day integration becomes a permanent fork.
Test the two things that break silently. A feature test asserting 403 for a non-authorized user on both the dashboard and the JSON API, and a test asserting that a logged value containing HTML renders escaped rather than interpreted.
Authorization is documented, not necessarily shipped. The authorization guide tells you how to gate; that is not the same as a deny-by-default route. Assume you own the gate regardless of what the docs imply, verify it on a fresh install, and re-verify after upgrades. This is the single highest functional risk in the package.
The JSON API is a second boundary. It exposes the same diff payloads over a different path. It may not inherit the Blade-oriented middleware assumptions you configured for the dashboard. Gate it explicitly, confirm it responds identically to your gate, and add it to your threat model — a reviewer will ask about it.
Diffs contain raw before/after values, including PII. Blade escaping should handle rendering, but escaping does not solve exposure. The correct fix is upstream: don't log the columns you don't want to display (logOnly, skip-attributes, or attribute-level redaction at the model). Redacting in the UI is a losing game because the API and exports bypass your Blade views. Decide what's sensitive before you turn on a viewer that makes it browsable.
Export dependencies: read composer.json, don't infer from docs. The docs describing CSV/JSON/Excel/PDF tells you nothing about whether dompdf or maatwebsite/excel are hard requirements. Confirm they're suggest; if they're require, you've just added a rendering stack to production for a feature you may not use.
Empty or confusing dashboard = data problem, not a bug. Before filing anything, check log_name/event value spread in the table. Filters that look broken are usually a mismatch between what's in the table and what the filter assumes.
Index the table. Pagination defaults protect the page, not the query. On anything beyond trivial volume, add indexes covering (subject_type, subject_id), causer_type/causer_id, created_at, log_name, and event. This is where the "hundreds of thousands to low millions of rows" claim either holds or falls over.
Docs track main, not your tag. The VitePress site auto-deploys from main, so published guidance can describe behavior you don't have. Pin your reading to the release tag you installed — and pin the dependency itself in composer.json rather than floating on ^. This matters more than usual given a single maintainer and no dependents.
Tailwind collisions are real. Precompiled assets may conflict with host styles (base resets, utility specificity, dark-mode class strategy). If the dashboard looks subtly wrong, scope the package's CSS to its layout container rather than escalating specificity across your app. Check dark-mode behavior explicitly if your app has its own theme toggle.
Route collisions and caching. An /admin prefix, a catch-all route, or a stale route:cache can all make the dashboard 404 or shadow an existing route. After configuring the prefix, run php artisan route:clear && php artisan config:clear before debugging anything else.
Treat it as an internal tool with an exit ramp. Bus factor 1, 16 stars, 0 dependents. Practical mitigations: pin the version, put the mounting behind a config flag or feature toggle so you can disable the route group and remove the provider in one deploy, and keep the code surface small enough that a fork is a contained decision. The data model is Spatie's — the UI is disposable, and that should stay true in your architecture.
Do not present it as tamper-evident. It is a read/browse UI over application-level rows, not an immutable ledger. If audit readiness is the driver, say "audit viewer" to stakeholders and auditors, and keep the WORM/immutability question with your actual security logging strategy.
Verify release metadata against Packagist/GitHub before pinning. The advertised release date on this package post-dates the current period — confirm the tag, the commit, and the composer.json constraints directly rather than relying on the listing. It costs two minutes and prevents pinning to something that doesn't resolve.
How can I help you explore Laravel packages today?