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 Blame Laravel Package

bernskiold/laravel-blame

Laravel Blame adds automatic user attribution to Eloquent models, tracking who created, updated, or deleted each record through simple traits and relationships. Ideal for audits and accountability with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

1.0.0 is the first stable release of bernskiold/laravel-blame, so there is no upgrade path to worry about — treat this as a greenfield install and pin "bernskiold/laravel-blame": "^1.0".

composer require bernskiold/laravel-blame
php artisan vendor:publish --tag="blame-config"   # if the package ships config
php artisan vendor:publish --tag="blame-migrations"
php artisan migrate

Then opt a model in:

use Bernskiold\LaravelBlame\Traits\Blameable;

class Post extends Model
{
    use Blameable;
}

Where to look first

  • config/blame.php — column names, guard resolution, and whether blame is enabled globally. Read this before touching a model, because the defaults decide your schema.
  • The published migration stub — check the column types and nullability before running it against a populated table.
  • The Blameable trait itself (vendor/bernskiold/laravel-blame/src) — at a 1.0.0 the source is the documentation; the README is unlikely to cover every edge.

First use case: attributing authorship on a record and surfacing it in the UI.

$post->creator;   // BelongsTo User
$post->updater;   // BelongsTo User
$post->deleter;   // if soft deletes are tracked

// In a controller / resource
PostResource::make($post->load('creator'));

Use it for "Created by Erik · 2 hours ago" in an admin table, and for "who touched this invoice?" support tickets. It is a lightweight attribution layer, not a full audit trail — if you need field-level before/after diffs, pair it with owen-it/laravel-auditing or spatie/laravel-activitylog rather than replacing them.

Implementation Patterns

Trait-per-model, not global. Add Blameable only to models where attribution is meaningful. Applying it at a base-model level drags extra belongsTo relations and two extra writes into every table in the app.

Resolve the actor explicitly in non-HTTP contexts. The most common real-world pattern is a job or command that must attribute work to a user who is not the authenticated one:

// In a queued job
Blame::use(fn () => $this->actingUser);   // or the package's resolver API

Post::create([...]);   // attributed to $this->actingUser

Wrap this in try/finally (or the package's withoutBlame() helper) so a long-lived queue worker never leaks one job's user into the next.

Backfill existing rows. For a live table, publish the migration but add the columns as nullable and backfill out-of-band:

DB::table('posts')->whereNull('created_by')
    ->update(['created_by' => $systemUserId]);

Do the backfill before you make the columns non-nullable in a follow-up migration. This keeps the deploy non-locking and reversible.

Seeders and tests. Factories will attribute to whoever is authenticated — usually nobody, which is fine if the columns are nullable. When you need deterministic data:

$this->actingAs($admin);
$post = Post::factory()->create();

// or explicitly avoid attribution noise
Post::withoutBlame(fn () => Post::factory()->count(50)->create());

Eager-load in list endpoints. Every blamed model adds one or two relations. Post::with('creator:id,name')->paginate() prevents the N+1 that appears the moment you render an author column in a table.

Reporting. Because attribution lands in real columns, ordinary SQL works — "records created per admin this month", "stale records nobody has updated":

Post::where('updated_by', $user->id)
    ->where('updated_at', '>', now()->subDays(30))
    ->count();

Consistent JSON shape. Expose a small, stable representation rather than the full user model:

'created_by' => $this->whenLoaded('creator', fn () => [
    'id' => $this->creator->id,
    'name' => $this->creator->name,
]),

Gotchas and Tips

  • Query-builder writes bypass blame. Post::where(...)->update([...]) and DB::table('posts')->update() fire no model events, so updated_by stays stale. Bulk operations must set the column manually — this is the single most common source of wrong attribution data.
  • $model->save() vs $model->update(). Both go through Eloquent and are covered, but increment(), touch(), and raw update() calls on the query builder are not.
  • Console, queue, and scheduler runs have no authenticated user. Decide deliberately: null, or an explicit "system" user. Pick one convention at the start — mixing both makes reports useless. A dedicated system user ID is usually the better default.
  • Guard and multi-auth apps. If you have separate web and admin guards, verify which one the package resolves by default and override it in config. Silently attributing admin actions to a logged-in front-end user is easy to miss in testing.
  • Nullable columns are your friend on day one. Make them nullable at first, backfill, then tighten. On Postgres, adding a NOT NULL column with an FK to a large table will take a lock and can time out mid-deploy.
  • Foreign keys on user deletion. restrictOnDelete() will block deleting a user who ever created a record — usually not what you want. Use nullOnDelete() and render "Deleted user" in the UI.
  • Soft deletes and forceDelete. If deleting is tracked, confirm which path sets it and that forceDelete() behaves as expected; the audit value of deleted_by disappears if rows are hard-deleted.
  • Config caching. After changing guard or column-name config, run php artisan config:clear — a cached config is a frequent reason "the setting has no effect" in production.
  • Column name changes after go-live are painful. Settle on created_by / updated_by / deleted_by up front. Renaming later means a migration plus a data update plus touching every query that referenced them.
  • Extension points to know: the user resolver (for system/service accounts), withoutBlame() for scoped suppression, and — if the package allows it — a custom user model reference so attribution points at your own User and not the package's default.
  • Version pinning. 1.0.0 is the first stable tag and its API is fresh; ^1.0 is reasonable, but read the changelog (the release notes link only to it) before jumping minors, since a 1.x may still adjust trait method names or config keys.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin