Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Accountable Laravel Package

testmonitor/laravel-accountable

Laravel package that makes Eloquent models accountable by tracking which user created, updated, or deleted each record. Adds configurable audit relations and helpers so you can see who did what, when, and query models by responsible user.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require the package, then publish its config and migration: php artisan vendor:publish --provider="...UserstampsServiceProvider" (check config/userstamps.php and database/migrations/*_add_userstamps_to_*.php before running php artisan migrate).
  • Open config/userstamps.php first. It controls the user model, the column names (created_by, updated_by, deleted_by by default), and whether the migration adds real foreign keys. Decide this before migrating in any shared environment.
  • Add the package trait to each model that needs attribution:
use Illuminate\Database\Eloquent\SoftDeletes;
use Vendor\Userstamps\Traits\HasUserstamps;

class Post extends Model
{
    use HasUserstamps, SoftDeletes;
}
  • First use case: in an authenticated controller, just Post::create($data). created_by and updated_by are populated from the current auth user with no extra code, and deleted_by is set on delete().
  • If your models do not use SoftDeletes, the deleted_by column is dead weight — either skip it in the migration or accept the unused column.
  • First thing to try in php artisan tinker is the new whileActingAs helper, since it lets you exercise stamping outside a real HTTP request:
whileActingAs($user, fn () => Post::create(['title' => 'Seed post']));

Implementation Patterns

Let the trait do the work; don't hand-assign stamps. The normal flow is create/update/delete/restore on models with the trait. Everywhere else you start writing $model->created_by = auth()->id() you are duplicating package logic and will drift.

Attribution for non-request contexts. Console commands, seeders, imports, and jobs have no auth()->user(), so stamps are null unless you act. Use the new callback helper to scope impersonation and keep it local:

whileActingAs($importer, function () use ($rows) {
    foreach ($rows as $row) {
        Article::create($row);
    }
});

Because it is a callback, the acting user is reverted automatically — no try/finally in your own code and no leaked state into the next operation in the same process.

Queue jobs are the biggest real-world gap. A dispatched job runs in a worker with no auth context. Either pass the acting user's ID in the job payload and wrap handle() in whileActingAs(User::find($this->actorId), ...), or leave stamps null and accept that "system" writes are unattributed. Pick one policy per app and be consistent — mixed attribution is worse than none.

Data imports and backfills. This is where 10.1.0's "don't override an already-set userstamp" matters most. If you're re-importing rows that already carry created_by, or copying records with replicate(), the existing value now wins instead of being clobbered by the current user. That makes idempotent re-imports and record duplication behave correctly out of the box, but it also means a fresh create() where you deliberately set created_by will keep your value rather than the auth user's — which is what you want for migrations that preserve history.

Reading the data. Expose relations and eager load them:

$posts = Post::with('createdBy', 'updatedBy')->latest()->paginate();

Use ->withTrashed() on the deletedBy relation, otherwise deleted users render as blank "deleted by" cells in admin screens.

Testing. In feature tests, $this->actingAs($user) covers HTTP paths. For unit-level model tests, whileActingAs($user, fn () => ...) is cleaner than juggling Auth::login()/logout() around assertions, and assertDatabaseHas('posts', ['created_by' => $user->id]) reads well.

Audit-style queries. Stamp columns are just integer FKs, so Post::where('created_by', $user->id)->count() and group-bys for "activity by user" dashboards are trivial. Add indexes on created_by/updated_by if you build those screens.

Gotchas and Tips

  • Silent nulls. No auth context means no stamps, with no warning. The usual culprits: queue workers, scheduled commands, model factories in seeders, and php artisan migrate-time data migrations. Audit these deliberately.
  • Query builder bypasses the trait. Model::where(...)->update(), insert(), DB::table(), and upserts skip model events entirely — no stamps. Load and save models, or accept missing attribution.
  • Mass assignment is an attack surface. If created_by/updated_by are in $fillable, a crafted request can forge attribution. Keep them out of $fillable and let the package write them; if you need explicit assignment for imports, use forceFill()/setAttribute() in trusted code only.
  • Multi-guard apps. The package resolves the default guard's user. If admins authenticate on a separate guard, stamping will pick up the wrong (or no) user. Check the config for a guard/user-resolver hook and point it at the right guard, or drive it through whileActingAs.
  • Foreign key pain. The published migration adds FKs to users. On a non-standard user table, rename it in config before migrating. Prefer nullOnDelete semantics so deleting a user doesn't cascade-delete or block on audit rows; the default restrict will bite you when someone deletes an old account.
  • Large tables. Adding three nullable indexed FKs locks or rewrites big tables on some engines. Use an online-DDL tool or add columns and indexes separately in production.
  • whileActingAs scope. It's a callback, so don't return early in a way that skips the closure's completion path, and verify nested usage if you wrap an importer that itself wraps calls — nesting should restore the outer actor, but confirm in a test rather than assuming. Also remember it does not change what else sees auth()->user() unless the package swaps the resolver; if you rely on auth()->id() in your own code inside the callback, test it.
  • Behavior change in 10.1.0. Pre-set stamps are no longer overwritten. Existing tests that set created_by and asserted it got replaced will now fail; existing "re-stamp everything on save" expectations need revisiting. This is almost always the better behavior, but it is a behavior change — scan for it during upgrade.
  • Disabling per operation. For system writes that shouldn't be attributed to whoever happens to be logged in (admin-triggered reindexing, sync jobs), look for the trait's disable/without-stamps method and wrap the operation rather than nulling columns afterwards.
  • Upgrade check. 10.1.0 is additive (whileActingAs, non-overriding stamps) with no deprecations listed, so it should be a low-risk composer update. After upgrading, add one test asserting that an explicitly-set created_by survives a save, and one asserting whileActingAs restores the previous actor — those two tests encode everything this release changed.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package