laravel-enso/track-who
Laravel Enso Track Who automatically records which users created, updated, or deleted Eloquent models. It provides traits and relationships for creator, updater, and deleter auditing, making it easy to track accountability across your Laravel application.
Three steps to first tracked model:
Install the package and publish its migration/ config:
composer require laravel-enso/track-who
php artisan vendor:publish --tag=track-who
php artisan migrate
(Publish tag name and whether a migration exists depends on version — inspect vendor/laravel-enso/track-who/src for the actual provider if the tag isn't found.)
Add the columns created_by, updated_by (and deleted_by if you use soft deletes) to the tables you want to track. The package ships a migration helper/stub, but for existing tables you'll usually add these manually with a nullable unsignedBigInteger + foreign key to users.id.
Drop the trait on the model:
use LaravelEnso\TrackWho\App\Traits\CreatedBy;
use LaravelEnso\TrackWho\App\Traits\UpdatedBy;
use LaravelEnso\TrackWho\App\Traits\DeletedBy;
class Post extends Model
{
use CreatedBy, UpdatedBy, DeletedBy;
}
If your version exposes a combined TrackWho trait, prefer that and opt out per-behaviour via config.
First use case: log in as a user, create a Post, then inspect the created_by column — it should equal auth()->id(). Add updated_by and deleted_by and repeat the create/update/soft-delete cycle to confirm.
Where to look first:
src/App/Traits/* — the actual hooks (bootCreatedBy, etc.) live here; read them before trusting the README.src/App/ServiceProvider.php and config/track-who.php — confirm the user model, table, and foreign key names, since they're configurable.auth()->check() returns true on the write path.Full audit trail per model. Use all three traits together on content tables (posts, comments, invoices) where you need to answer "who created/edited/deleted this?" years later. The trait hooks fire on creating, updating, deleting events, so no controller changes are required.
Selective tracking. Don't blanket-apply. Only add UpdatedBy to models where edits matter; skip DeletedBy unless the model uses SoftDeletes (otherwise the hook is a no-op but you still pay a column and a relation).
Querying by author. The traits typically expose relations (createdBy(), updatedBy(), deletedBy()) returning BelongsTo(User::class). Use them for eager loading in list views:
Post::with(['createdBy', 'updatedBy'])->latest()->paginate();
If the relation names aren't what you expect, grep the trait for function createdBy — Enso sometimes uses user naming that collides with app relations.
Seeders, factories, and console commands. These run without an authenticated user, so the columns will stay null. Either wrap setup in actingAs/Auth::login(), or accept nulls and make the foreign key nullable. For backfills, prefer an explicit job that sets the column rather than relying on the trait.
API / queue / scheduled work. Same story: auth() is empty in queued jobs and scheduled commands. If you need an "owner" for background writes, pass the user id through and set the column explicitly before save; the trait will only overwrite it when auth()->check() is true.
Enso integration. The package is designed to slot into the wider Enso stack (enso/core, enso/helpers, role/permission middleware). If you're already running Enso, it will pick up the authenticated user from Enso's own auth chain, which is convenient — but it also means the package's assumptions about the user table (users, id) match Enso's defaults, not necessarily yours.
Soft-delete restore. A restore() typically leaves deleted_by set. If you want "who restored" tracking, you'll need an extra column and a custom trait — the package doesn't handle it out of the box.
created_by, updated_by, deleted_by are not in $guarded = [] and the trait sets them via ->update() rather than direct property assignment, the write fails silently. In practice $guarded = [] or explicit $fillable entries are the safest bet; check the trait source to see which path it uses.Post::where(...)->update([...]) does not fire model events, so no updated_by is written. If audit accuracy matters, use model instances or an observer plus a manual stamp on bulk queries.forceDelete() also bypasses deleting. Permanent deletes leave deleted_by at its previous value (or null). Only delete() on a SoftDeletes model is tracked.->constrained()->restrictOnDelete() on the author FK will block deletion. Use nullOnDelete() or cascadeOnDelete() deliberately — the package doesn't choose for you, and Enso's User model may or may not have its own handling.created_by FK migration after users exists. In fresh installs this is fine; in existing projects migrating an old table you must sequence it manually.createdBy() accessor or a user() relation used elsewhere, the trait's BelongsTo may clash. Rename via config or drop the trait and add a local relationship instead.$this->actingAs($user) before factory()->create() is required if any assertion checks created_by. Otherwise the factory-created record has a null author and tests are flaky between local and CI (where a session might leak).track-who.users.model (or equivalent key) is wrong, the trait will throw a class-not-found at runtime, not at boot — this often shows up only when the first tracked save happens. Publish and inspect the config once per project rather than relying on defaults.public static function bootCreatedBy() style, and you can redeclare them in the model to short-circuit. That's cleaner than forking the package.spatie/laravel-activitylog or similar; track-who is a lightweight complement, not a replacement.How can I help you explore Laravel packages today?