laravel-activitylog: the migration assumes integer keys, and the config file is optional
Log activity inside your Laravel app
At a glance
- What is it?
- A Spatie package that records what happened in a Laravel application into a single activity_log table, with automatic model event logging. The schema decision that trips people up is stated in one italic note: the default migration writes integer subject_id and causer_id columns, and UUID applications have to edit it before migrating.
- Who is it for?
- laravel-activitylog suits a Laravel application that needs an audit trail readable from PHP, where an admin panel will query who did what to which record, and it earns its place with model event logging alone since that removes the bookkeeping from every save call. Two things to check before wiring it in.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- spatie/laravel-activitylog5,917 ★ · +16
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| spatie/laravel-activitylog | 5.9k | 5.9k |
16 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The default migration assumes integer keys and tells you to edit it
The one piece of real caution in the readme is an italic note under the migration step. The default migration assumes you are using integers for your model IDs, and if you are using UUIDs or some other format you should adjust the format of the subject_id and causer_id fields in the published migration before continuing. Before continuing, not after. So the first thing an application with non-integer keys has to do is copy the migration out of the vendor directory and edit it, and the package gives no alternate migration for that case. Both key columns are affected, which means the actor side and the subject side are both hardcoded to the same assumption. The migration is published with its own tag:
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"After publishing, php artisan migrate creates the activity_log table.
The config file is published separately, so skipping it means package defaults
Publishing the migration and publishing the configuration are two distinct operations with two distinct tags. The migration one is required, because the table has to exist. The config one is described as optional:
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"The consequence of skipping it is that the application runs on whatever defaults the package ships, and the readme does not enumerate them. So the two failure modes are asymmetric: forget the migration and the table is missing, forget the config and nothing errors at all. That is the kind of default worth reviewing once at install time rather than discovering when a description string comes out in a shape you did not expect. The package registers itself automatically, so there is no manual service provider line to add, and the config/ directory at the repository root is where those defaults live.
Model event logging stores an attributes and old pair, not a diff
The automatic logging path is the one worth understanding, because the shape of what gets stored is not what you would assume. Changing a name on a model and saving causes an activity to be logged, whose description returns updated and whose subject returns the saved model instance. The stored changes are then read back through an attribute_changes accessor, and the collection it returns is two parallel arrays rather than a computed difference:
[
'attributes' => [
'name' => 'updated name',
'text' => 'Lorem',
],
'old' => [
'name' => 'original name',
'text' => 'Lorem',
],
];Both arrays carry the text key with the same value, because the two arrays are the state after and the state before, not the changed keys alone. Anything unchanged is present in both, so a consumer has to compare the two itself to know what actually moved. That is a deliberate choice for storage, since a before-and-after snapshot needs no join to reconstruct, but it does mean the record grows with model width rather than with change size.
performedOn and causedBy are two independent relations on the same row
The advanced example chains four calls before logging, and the two relations are the part that matters structurally. performedOn takes the model the activity happened to, causedBy takes the actor, withProperties attaches an arbitrary key and value pair, and log writes the description. Reading it back is symmetrical, with a subject accessor returning an instance of the Eloquent model, a causer accessor returning an instance of the user model, a description returning the string, and a getProperty call taking the key and returning the stored value. So one row holds two polymorphic relations pointing at two different tables, and they are unrelated to each other: nothing in the call chain requires the causer to have permission over the subject, and the example does not show a case where either side is null even though both are optional in an audit context.
Two php-cs-fixer configs and a PHPStan baseline ship side by side
The static analysis and style setup is more layered than a package this size usually needs, and the file names are the evidence. Style is configured twice, with .php_cs.dist and .php_cs.laravel both at the repository root, which is the naming pair for a package whose style has to satisfy both its own conventions and the Laravel preset. Static analysis has phpstan.neon.dist alongside a phpstan-baseline.neon, and the baseline file is the interesting part: it exists to record the errors the code currently produces so that level can be raised without a large cleanup commit, which means the committed baseline is a to-do list of known type problems. Tests run through phpunit.xml.dist, and the readme exposes that as a single composer script, composer test, rather than showing the phpunit invocation.
Version 5 landed in March and the credits name who carried version 4
The release history shows three recent tags: 5.1.1 on 2026-09-08, then 5.1.2 and 5.1.3, both on 2026-10-19, and the last push to the default branch is 2026-10-26. The repository is not archived. The credits section is unusual in naming specific contributors with roles: Freek Van der Herten, Sebastian De Deyne and Tom Witkowski are listed, followed by all contributors. It then records two separate debts, thanking Ahmed Nagi for all the work on v4 and Caneco for the original logo.
Two upgrade documents exist and neither is described
Both CHANGELOG.md and UPGRADING.md sit at the repository root, and the readme points at each with a single line and no summary of either. The changelog line says to see it for more information about recent changes, and the upgrading line says to see UPGRADING.md for details. Neither line says which versions the upgrade document covers, so a reader on an older major version has no way to tell from the readme whether a migration note exists for them. The distinction between the two files is the one that matters: a changelog records what changed, an upgrade document records what a consumer has to do about it, and the readme does not tell you which of those questions either file answers for your version.
Editorial conclusion
laravel-activitylog suits a Laravel application that needs an audit trail readable from PHP, where an admin panel will query who did what to which record, and it earns its place with model event logging alone since that removes the bookkeeping from every save call. Two things to check before wiring it in. Decide your key format first, because the published migration assumes integer model IDs and a UUID application has to edit the subject_id and causer_id columns before migrating, which is a schema decision rather than a configuration flag. And note that the config file is published separately from the migration with its own tag, so an application that skips that step runs with package defaults it never chose. A team wanting change history as a full versioned document needs more than this single-table design gives.
Frequently asked questions
How do I log activity in Laravel?
Install the package with composer require spatie/laravel-activitylog, which registers itself automatically, then publish the migration with the activitylog-migrations tag and run php artisan migrate. Activity is then written with a chain such as activity()->performedOn($model)->causedBy($user)->withProperties([...])->log('a description'), and read back through the Activity model.
Where does laravel-activitylog store its records?
All activity goes into the activity_log table, and the records are retrieved through the Spatie\Activitylog\Models\Activity model, with Activity::all() returning the collection. Each row carries a subject relation to the model involved, a causer relation to the actor, a description string, and any custom properties.
Can laravel-activitylog be used with UUID model keys?
Yes, but not with the default migration as published. The default assumes integer model IDs, and the readme instructs you to adjust the format of the subject_id and causer_id fields in the published migration before continuing if you use UUIDs or another format.
Does laravel-activitylog require publishing a config file?
No, the config publish step is described as optional and uses the activitylog-config tag, separate from the required migration publish. An application that skips it runs on the package defaults, and the readme does not enumerate what those defaults are.
How does automatic model event logging record a change?
Saving a modified model causes an activity to be logged with a description of updated and a subject pointing at the saved instance. Reading attribute_changes returns two parallel arrays, an attributes key holding the state after the save and an old key holding the state before it, so unchanged keys appear in both.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-laravel-activitylog)Star history chart for your README
[](https://hysenlabs.com/projects/spatie-laravel-activitylog)Was this useful?