alive2tinker/resource-activities
Resource Activities is a Laravel package for tracking and displaying activity on your Eloquent resources. It helps you log resource changes and surface clear activity timelines, making it easy to see who did what and when with minimal setup. Designed for quick integration into Laravel apps.
The package is a Laravel Nova resource tool: it renders an activity/audit panel inside a Nova resource's detail or update screen. It is intentionally small (4 stars, 0 dependents), so budget ten minutes to read vendor/alive2tinker/resource-activities/src end-to-end before wiring it up — you will understand the whole package faster than you can find blog posts about it.
Confirm the two hard dependencies. Open the package composer.json and check the constraints on laravel/nova and, almost certainly, spatie/laravel-activitylog. Your app must already be logging activity for the tool to have anything to display:
composer require alive2tinker/resource-activities
composer require spatie/laravel-activitylog # if not already present
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
Make the model loggable. On the Eloquent model behind your Nova resource:
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Invoice extends Model
{
use LogsActivity;
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()->logOnlyDirty()->logFillable();
}
}
Register the tool on the resource. Typical Nova resource-tool registration happens in the resource's fields() method (resource tools are not returned from tools(); that method is for the sidebar/nav tools on NovaServiceProvider):
public function fields(Request $request)
{
return [
ID::make()->sortable(),
// ... fields
\Alive2Tinker\ResourceActivities\ResourceActivities::make(),
];
}
Verify the exact class name and namespace by listing the source directory; the README may be stale relative to the code.
Publish assets if the tool ships compiled JS/CSS. Nova 4+ uses Vite; older tools assume Laravel Mix and nova:publish. Check for dist/ or resources/js/ in the vendor folder.
First useful case: open a record you have edited a few times in Nova and confirm the panel lists entries with causer, timestamp, and changed attributes. If it is blank, the problem is almost always step 1/2 (no rows in activity_log), not the tool.
Attach selectively, not everywhere. Resource tools appear on every detail page of the resource they are declared on. Adding it to a high-traffic resource (e.g. Order with thousands of views/day) means an activity query on every detail load. Prefer adding it to records where audit history has real value — invoices, contracts, user accounts — and skip it on join tables and lookups.
Gate by ability. If the timeline can expose sensitive diffs, wrap it in the resource's authorization flow rather than relying on the tool's own checks:
public function fields(Request $request)
{
return array_filter([
ID::make()->sortable(),
$request->user()->can('audit', Invoice::class)
? ResourceActivities::make()
: null,
]);
}
Tame the log volume with logOnlyDirty() and logOnly(). Out of the box LogsActivity can be noisy (every updated_at touch, every save). Constrain it at the model so the tool renders a readable timeline:
LogOptions::defaults()
->logOnly(['status', 'amount', 'assigned_to'])
->logOnlyDirty()
->dontSubmitEmptyLogs()
->setDescriptionForEvent(fn (string $e) => "invoice {$e}");
Pair it with a Nova Action or Observer for intentional events. For domain milestones you want in the timeline without a diff, log manually:
activity()
->performedOn($invoice)
->causedBy(auth()->user())
->withProperties(['from' => 'draft', 'to' => 'sent'])
->log('invoice sent to customer');
These entries flow into the same tool panel and read better than raw attribute diffs.
Customize through the published view. If the panel shows too much (raw JSON properties, causer email), publish views/config with php artisan vendor:publish --provider="Alive2Tinker\ResourceActivities\ResourceActivitiesServiceProvider" and edit the Blade partial. This is the primary extension point for a package this size — expect to fork rather than configure.
Keep it adjacent to Nova's native tools. Don't replace Nova's built-in "view changes" affordances in the update form; the resource tool is a read-only history view. Use it for "what happened over time," and leave inline diffing to Nova.
Patch locally via a path repository when you need a fix that upstream will not ship (likely, given the star count):
"repositories": [
{ "type": "path", "url": "../forks/resource-activities" }
]
Test in a feature test, not by clicking. Assert that the activity panel's underlying data is correct:
$this->actingAs($user)->put("/nova-api/invoices/{$invoice->id}", ['status' => 'sent']);
$this->assertDatabaseHas('activity_log', ['subject_id' => $invoice->id, 'event' => 'updated']);
composer.json pins Nova 5 and install fails, treat the code as a reference implementation and copy the tool class into app/Nova/Tools/ rather than fighting constraints.select count(*) from activity_log where subject_type = 'App\\Models\\X' → is the model using LogsActivity? → is logOnly filtering out the fields you changed? → is activity_log actually migrated? The tool is downstream of all of that.<div> with no JS error, check the browser console for a 404 on /nova-api/... or a missing /vendor/resource-activities/*.js. Run php artisan nova:publish and, depending on the build setup, npm run dev inside the package directory.fields() vs tools() confusion. Nova's docs distinguish resource tools (returned from fields()) from global tools (registered on NovaServiceProvider::tools()). Putting the class in tools() yields a "class not found in resource" error or silently does nothing.causer (usually a User) per activity row. On a record with 200 log entries that is 200 queries unless the tool eager-loads — verify in src/ and, if absent, cap the displayed entries in the published view or add ->with('causer') in your own patched copy.spatie/laravel-activitylog ships activitylog:clean with a 365-day default. If compliance requires longer history, edit config/activitylog.php (delete_records_older_than_days) or exclude the command from the scheduler — otherwise the panel quietly starts at "1 year ago" and nobody notices.null. Handle it in the Blade view ($activity->causer?->name ?? 'System').Relation::morphMap(), old subject_type values stored as FQCNs stop resolving and the tool's entries may point at nothing. Introduce the morph map before you start accumulating history, or write a backfill migration.NovaResource::authorizedToView. Verify with a second user who shouldn't see the record; if the panel leaks, patch the tool's query to go through the model (so global scopes apply) rather than a raw Activity::query().app/Nova/ResourceTools/ with a comment linking upstream, so a Nova upgrade doesn't block on an unmaintained Composer constraint.How can I help you explore Laravel packages today?