If you’re using spatie/laravel-activitylog, you’ve probably written something like activity()->event(…)->log(…) a hundred times without thinking about where each piece lands in the database. The fluent API is friendly, but the column mapping isn’t obvious until you go look — so here it is in one place.
The package writes to a single table called activity_log. Every chained method on the builder corresponds to one column on that row. 💡
1 2 3 4 5 6 7 | activity() ->useLog('SyncCampaignUsersJob') ->event('Sync user without detaching') ->performedOn($campaign) ->causedBy($actor) ->withProperties(['chunk' => '3/20', 'count' => 100]) ->log('Processing chunk 3/20 (100 users).'); |
That single fluent call writes one row. Here’s the full mapping:
| Method | Column(s) | What it stores |
|---|---|---|
| useLog(“string”) | log_name | Filterable bucket like “Auth” or “SyncCampaignUsersJob”. Never leave empty — defaults to the literal string “default”, which makes filtering useless. |
| log(“string”) | description | Free-form, human-readable message. Returned by $activity->description. |
| event(“string”) | event | Short verb-ish label like “created”, “updated”, “Synced user with detaching”. Useful for grouping similar actions. |
| performedOn($model) | subject_type + subject_id | Polymorphic reference to the affected model. e.g. “App\Models\Campaign” + 6. |
| causedBy($user) | causer_type + causer_id | Polymorphic reference to the actor. Pass a model instance or just the ID. |
| withProperties([…]) | properties | Arbitrary JSON. Great for structured context: counts, IDs, batch labels, before/after diffs. |
The empty-useLog gotcha 🪤
Here’s the failure mode worth burning into memory. This call:
1 2 3 4 5 | activity() ->event('Sync user without detaching') ->performedOn($campaign) ->causedBy($actor) ->log('Synced 100 users.'); |
…silently writes log_name = “default”. Six months later you open the activity log dashboard, filter by log name, and you’re staring at 47,000 rows in the default bucket. Always add useLog() with a meaningful string. The class name of the job or service writing the log is a perfectly fine default — future-you will thank present-you when grep’ing through audit history.
One more nuance: causer_type
If you pass an integer to causedBy(), the package needs to know what model that ID points to. By default it assumes your auth user model (set in config/auth.php). If your causers are sometimes a User and sometimes a SystemActor or a tenant model, pass the model instance instead of the ID — the polymorphic columns will resolve correctly and querying back becomes painless.
That’s the whole mental model: one row, one chained call, one column per method. Keep useLog() populated and you’ll have a queryable audit trail instead of a blob of “default” entries. 🎯