velitsol/userstamps
Velitsol Userstamps automatically records who created, updated, or deleted each Eloquent model, adding created_by, updated_by, and deleted_by columns without extra code. A lightweight Laravel trait for simple, reliable user attribution across your application.
This is an archived fork-lineage package (a wildside/userstamps derivative), so treat it as vendored code you own rather than a maintained dependency. Install it anyway:
composer require velitsol/userstamps
Then run the publishable migrations (check vendor/velitsol/userstamps/database/migrations if the provider doesn't publish):
php artisan vendor:publish --provider="Velitsol\Userstamps\UserstampsServiceProvider"
php artisan migrate
Add the trait to the model you want to stamp, and make sure the model's table has created_by, updated_by, and deleted_by columns:
use Velitsol\Userstamps\Userstamps;
class Post extends Model
{
use Userstamps;
}
First use case: create a post while authenticated — auth()->id() lands in created_by automatically, and $post->creator returns the User model. That's the whole value proposition: drop-in audit columns alongside created_at/updated_at.
Reading stamps back. The trait exposes relationships and convenience accessors:
$post->creator; // User who created
$post->updater; // User who last updated
$post->deleter; // User who soft-deleted (only meaningful post-delete)
$post->createdBy; // or ->updatedBy / ->deletedBy
Eager-load them when listing (Post::with('creator')->paginate(50)) — otherwise you N+1 across every row.
Custom column names. If you can't use the defaults (e.g., existing author_id), override via the config file or by overriding the trait's resolver methods on the model. Config keys typically mirror userstamps.created_by_column etc. Verify against the fork — config key names drift between wildside and this fork.
Custom user resolution. In queues, console commands, or tests there is no auth()->user(). Set the resolver once in a service provider or AppServiceProvider::boot():
Userstamps::resolveUserstampUsing(fn () => auth()->id() ?? 1);
Or, for queued jobs, pass the acting user's ID into the job and set it inside handle().
Soft deletes. The trait hooks the deleting and restoring events. Make sure your model uses SoftDeletes and the Userstamps trait, and that deleted_by is in the table. Restoring a record zeroes deleted_by and updates updated_by.
Relations on the User side. Add hasMany for createdPosts() etc. if you need reverse lookups — the package doesn't add these to your User model automatically.
Bulk operations. Post::where(...)->update([...]) bypasses Eloquent events, so stamps are skipped. Prefer ->get()->each->update(...), or accept it and document that raw queries aren't stamped.
vendor/velitsol/userstamps/src/Userstamps.php) before adopting; if it's the wildside code, expect Laravel 8-era assumptions. Consider vendoring it into app/Support/ or app/Traits/ and dropping the composer dependency.unsignedBigInteger or unsignedInteger — match your users.id type. Mismatches fail silently or throw cryptic FK errors on Postgres.forceFill/direct property set in most versions, so you generally don't need $fillable entries. But if you copy columns into a DTO or toArray(), you'll see them appear — hide with $hidden if leaking them to APIs is a concern.created_by/updated_by are null. That's usually fine but breaks "who did this?" audits. Guard with ->whereNull('updated_by') health checks or set a system user ID in the resolver.Userstamps::resolveUserstampUsing(fn () => User::factory()->create()->id) cautiously, or actingAs($user) in tests.$this->actingAs($user)->post(...) works; assert with assertDatabaseHas('posts', ['created_by' => $user->id]). For unit-level trait tests, set the resolver statically and reset it in tearDown().getUserstamp(), getCreatedByColumn(), etc., override them per-model rather than editing config globally when only one table needs different columns.users table exists. If you're on a fresh install and hit FK errors, reorder or guard with Schema::hasTable('users').bootUserstamps() override with static::creating(fn($m) => logger($m)) to confirm the trait's event hooks are actually registered — the most common cause is a conflicting boot() method in your model that doesn't call parent::boot().spatie/laravel-activitylog, or a small local trait), or fork and pin the trait into your app. Both are ~80 lines of code — often cheaper than tracking an abandoned dependency.How can I help you explore Laravel packages today?