Activity Log
The activity log records who did what, and when, across users, roles and sign-ins. Entries read like this:
John Administrator Deactivated user Jane Doe September 30, 2026 · 10:42 PM
It’s built on spatie/laravel-activitylog v5 and stored in the activity_log table.
| Piece | File |
|---|---|
| Event catalog and logging API | app/Enums/ActivityEvent.php |
| Sign-in listener | app/Listeners/LogAuthenticationActivity.php |
| Viewer | app/Http/Controllers/ActivityController.php, resources/js/pages/activity/ |
| Authorization | app/Policies/ActivityPolicy.php (activity.view) |
| Retention | routes/console.php (activitylog:clean) |
Events
Section titled “Events”| Event | Value | Logged when | Example sentence |
|---|---|---|---|
| User created | user.created |
An admin creates a user, or someone signs up | Created user Jane Doe · Created their account |
| User updated | user.updated |
Name or email actually changes (admin or own profile) | Updated user Jane Doe · Updated their profile |
| User deleted | user.deleted |
Single delete, bulk delete (one entry each), deleting your own account | Deleted user Jane Doe · Deleted their account |
| User activated | user.activated |
Status changes to active | Activated user Jane Doe |
| User deactivated | user.deactivated |
Status changes to inactive | Deactivated user Jane Doe |
| Role assigned | user.role_assigned |
A role is added to a user (on create or edit) | Assigned role admin to Jane Doe |
| Role removed | user.role_removed |
A role is taken away on edit | Removed role user from Jane Doe |
| Password changed | user.password_changed |
Admin sets it, the user changes it, or a reset | Changed the password of Jane Doe · Changed their password · Reset their password |
| Permission changed | role.permissions_changed |
A role’s permission set changes | Changed permissions of role Editor |
| Login | auth.login |
Any successful sign-in (password, passkey, remember-me) | Logged in |
| Logout | auth.logout |
Signing out | Logged out |
| Failed login | auth.login_failed |
Wrong credentials, or an inactive account | Failed login attempt for jane@example.com |
Only real changes are logged. Saving a form without changes, or setting a status the user already has, writes nothing.
What an entry stores
Section titled “What an entry stores”| Column / property | Contents |
|---|---|
event |
The event value, e.g. user.deactivated |
causer_type / causer_id |
Who did it (the signed-in user by default), or NULL for anonymous failed logins |
subject_type / subject_id |
What it was done to (a User or Role), if anything |
properties.causer_name |
The actor’s name at the time |
properties.subject_name |
The subject’s name at the time |
properties.ip |
The request IP |
| other properties | Event details: role, changed, added/removed, email, reason, via |
Storing names means an entry still reads “Deactivated user Jane Doe” after Jane is renamed or deleted.
Viewing the log
Section titled “Viewing the log”Open Activity log in the sidebar (/activity). It’s a standard server-driven data table:
- Search matches the actor’s name, the subject’s name, or a failed-login email.
- Event filter (
?event=auth.login_failed,user.deleted). - Newest first, with a page size of 10 to 100.
Who can see it
Section titled “Who can see it”The page requires the activity.view permission (ActivityPolicy::viewAny). By default only superadmins have it. To share the log, grant activity.view to a role on the Roles page, or to admin in RolePermissionSeeder.
Non-superadmin viewers never see entries whose actor or subject is a superadmin, in line with Superadmin Visibility.
Logging an event
Section titled “Logging an event”Call log() on an enum case right where the action happens:
use App\Enums\ActivityEvent;
ActivityEvent::UserDeactivated->log($user);public function log(?Model $subject = null, array $properties = [], ?User $causer = null): void$subject: the model acted on. Itsnameattribute is saved assubject_name.$properties: extra details to store.$causer: defaults to the signed-in user. Pass one explicitly when nobody is signed in (e.g. registration, password reset) or when acting on someone’s behalf.
ActivityEvent::RoleAssigned->log($user, ['role' => 'admin']);ActivityEvent::PasswordChanged->log($user, ['via' => 'reset'], causer: $user);Customizing
Section titled “Customizing”Adding an event
Section titled “Adding an event”Say you want to log when a post is published.
-
Add a case, a label and a sentence in
ActivityEvent:case PostPublished = 'post.published';// label()self::PostPublished => 'Post published',// describe()self::PostPublished => __('Published post :name', ['name' => $subject]),describe()receives the stored entry;$subjectis itssubject_name. Give the post anameattribute, or pass['title' => $post->title]and read it with$activity->getProperty('title'). -
Log it where the action happens:
$post->update(['published_at' => now()]);ActivityEvent::PostPublished->log($post);
The new event appears in the Event filter automatically, since the options come from ActivityEvent::options(). Add a test in tests/Feature/ActivityLogTest.php.
Changing retention
Section titled “Changing retention”activitylog:clean runs daily (routes/console.php) and deletes entries older than 365 days. For that to happen in production, the Laravel scheduler must be running (see Deployment).
To keep entries for a different period, pass --days in the schedule:
Schedule::command('activitylog:clean --days=90')->daily();Or publish Spatie’s config and change clean_after_days:
php artisan vendor:publish --tag=activitylog-configTo keep everything forever, remove the schedule line.
Turning logging off
Section titled “Turning logging off”Set ACTIVITYLOG_ENABLED=false to stop writing entries, e.g. in a test environment. To remove the feature entirely, delete the log() calls, the listener, the controller and page, the activity.view permission (then run tachi:types), and the activity nav item.
Recording more context
Section titled “Recording more context”Everything in $properties is stored as JSON, so you can add details such as the user agent:
ActivityEvent::Login->log(causer: $user, properties: ['agent' => request()->userAgent()]);To add a property to every entry, add it to the defaults array in ActivityEvent::log() next to ip.