tomatophp/tomato-user-activities-module
A TomatoPHP module that logs and displays user activities in your Laravel app, giving admins a clear timeline of actions and events for auditing and monitoring.
The package is tomatophp/tomato-user-activities-module, a first tagged release (v1.0.0, March 2024) with no real release notes. That means the source in vendor/ is your documentation — budget a half-day to read it before writing feature code on top of it.
1. Install and confirm discovery.
composer require tomatophp/tomato-user-activities-module
TomatoPHP modules register via Laravel package auto-discovery. Verify the provider loaded before you debug anything else:
php artisan package:discover -v
php artisan about # look for the provider in the loaded list
If it's not listed, add the provider manually to bootstrap/providers.php (or config/app.php on older Laravel) using the class you find in the package's composer.json extra.laravel.providers block.
2. Publish config and migrations, then read them before running.
php artisan vendor:publish --tag=tomato-user-activities-module-config
php artisan vendor:publish --tag=tomato-user-activities-module-migrations
Run php artisan vendor:publish with no tag first to list what this version actually exposes — TomatoPHP tag naming drifts between modules, and a wrong tag silently publishes nothing. Open the published migration and note the columns: user_id, IP, user agent, timestamps, and any subject/polymorphic columns. Then:
php artisan migrate
3. Register the Filament plugin (if you want the admin browsing UI). In your panel provider:
->plugin(\TomatoPHP\UserActivitiesModule\UserActivitiesPlugin::make())
4. Locate the recording mechanism. Open vendor/tomatophp/tomato-user-activities-module/src and start at the service provider's register()/boot(). Look for Traits/, Models/, Listeners/, Observers/, and Middleware/. Whichever of those the provider wires up is your integration point.
First use case to prove it works end to end: log in as a test user, edit a record, then open the "User Activities" page the plugin registers in Filament. Confirm you see your user_id, the IP, the action, and a timestamp. If the row is missing, work backwards: model → listener/middleware → provider boot → config cache. That one round trip tells you more than the README.
One feature worth testing early: the package's description mentions each request being tagged with a request hash that you can correlate against standard Laravel logs. If a middleware is registered for this, drop a Log::info('...') in a controller and check whether the same hash appears in both the log line and the activity row. That correlation is genuinely useful during incident triage and it's cheap to verify.
Record at the boundary, not in controllers. If the package ships middleware, register it globally or on authenticated route groups in bootstrap/app.php / Kernel.php. If it ships a trait or observer, attach it to User and to the specific models you actually want audited — not AppServiceProvider::boot() over every model. Blanket application is the fastest way to a table you can't query and a page that takes four seconds to load.
Queue writes off the request path. Every activity row is an insert on the hot path. If the package exposes a config toggle for queuing, turn it on. If not, and your traffic is meaningful, register your own ShouldQueue listener against the events you care about and keep the vendor's synchronous path for the low-volume cases. On a login-heavy or API-heavy app, this is the difference between a 2ms overhead and a 40ms one.
Compose with spatie/laravel-activitylog rather than replacing it. This module is a user activity layer — who was doing what, from where. Spatie handles model-level before/after diffs. They answer different questions and coexist fine. If you need both, decide which table is authoritative for which question before you build dashboards on top, otherwise you'll have two sources of truth and no clear answer when they disagree.
Point dashboards at the module's widgets; don't rebuild the table. The Filament widget is the main reason to use this over a hand-rolled table. If you need extra columns or tenant scoping, subclass the widget and swap it in your panel provider — keep vendor/ untouched so composer update stays clean.
Resolve the guard explicitly in multi-guard apps. If you have separate web and admin guards, confirm which one the module reads from. Most TomatoPHP modules default to config('auth.defaults.guard'). If your admin panel authenticates against a different guard, every activity row will have a null or wrong user_id and the UI will look empty. Set it in the published config and write a test that asserts the causer is correct under the non-default guard.
Plan retention on day one. This table grows monotonically. Add pruning to your schedule immediately:
// routes/console.php or app/Console/Kernel.php
Schedule::command('model:prune', [
'--model' => [\TomatoPHP\UserActivitiesModule\Models\UserActivity::class],
])->daily();
If the vendor model doesn't implement Prunable, subclass it, add the trait plus a prunable() query scoped to your retention window (e.g. ->where('created_at', '<', now()->subDays(90))), and bind your subclass wherever the module resolves the model.
Wrap access behind your own interface. Create a thin ActivityRepository or query object in your app that the Filament page, any API endpoint, and any export all go through. This costs an hour and buys you a one-file swap if the package stalls or you outgrow it — which, at v1.0.0 with zero dependents, is a live possibility.
Testing. Use RefreshDatabase. Assert against the package model directly, never against rendered Filament tables — Blade changes between patch releases and your tests will break for no reason. Freeze time with travelTo() before asserting timestamps; activity rows are timestamp-sensitive and flake otherwise.
it('records a login activity', function () {
$user = User::factory()->create();
$this->actingAs($user)->post('/login');
expect(UserActivity::where('user_id', $user->id)->exists())->toBeTrue();
});
"tomatophp/tomato-user-activities-module": "^1.0" in composer.json, and read the diff of src/ on every minor bump. There is no documented upgrade path, and the v1.0.0 note is a changelog link with nothing described.composer.json in the package for whether this tag targets Filament v3 or v4 before installing into an existing panel. A mismatch surfaces as a confusing "class not found" in your panel provider, not as a clean dependency conflict — you'll spend an hour in the wrong file.user_id, event/action, and created_at. If those columns lack indexes, the page will crawl within a few months of real traffic. Add your own migration for missing indexes; don't edit the vendor one, or composer update will clobber it.php artisan config:clear after publishing config. A config cache from before install makes the plugin do nothing at all, which reads exactly like a broken package. Clear the cache before you start debugging the code.->timezone() setting before touching the module.php artisan filament:assets after install if the module ships views or JS. Otherwise the page renders blank with no error in the log — the classic silent-failure trap.Log::withContext(['request_hash' => ...]) is enough). That single line turns "user says it broke at 2pm" into a filtered query.vendor/tomatophp/tomato-user-activities-module/src, starting at the service provider. With thin docs and a single-author maintainer, that file is the spec. Trace boot() outward and you'll find the answer faster than any search.How can I help you explore Laravel packages today?