bernskiold / laravel-blame
Automatically track which user created and last updated your Eloquent models, with relations and schema macros.
Requires
- php: ^8.2
- illuminate/contracts: ^11.0 || ^12.0 || ^13.0
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0 || ^9.0
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- pestphp/pest: ^2.0 || ^3.0 || ^4.0
- pestphp/pest-plugin-laravel: ^2.0 || ^3.0 || ^4.0
- phpstan/phpstan: ^2.1
README
"Who changed this?" is a question every serious application eventually has to answer. This package quietly records the user behind each create, update and soft-delete — no manual created_by_id = auth()->id() scattered through your controllers, no forgetting to set it on that one form.
class Post extends Model { use Blameable; } $post->createdBy; // the user who created it $post->updatedBy; // the user who last touched it
Add a trait, add the columns, and the right user id is captured automatically on every save — with relations ready for eager loading and display.
Why you'll like it
- Set-and-forget. The acting user is captured through model events on create, update, soft-delete and restore. You never wire
auth()->id()by hand again. - Pick exactly what you need. Track the creator, the updater, the deleter — or all three with the combined
Blameabletrait. - Relations included.
createdBy(),updatedBy()anddeletedBy()are ready to eager-load and render. - Sensible, safe defaults. The creator is only set when empty (so you can override it), and the updater is left untouched when no user is authenticated — a background job won't wipe the last known editor.
- Works anywhere. A pluggable resolver lets you record the right user from queues, commands and imports, not just web requests.
- Configurable to the column. Bring your own column names, user model, and foreign key behaviour — globally or per model.
- Tidy migrations.
$table->blameable()adds the columns (and foreign keys) in one line.
Installation
You can install the package via Composer:
composer require bernskiold/laravel-blame
If you'd like to change the column names, the user model, or the foreign key behaviour, publish the config:
php artisan vendor:publish --tag=blame-config
Schema
Add the columns with the blueprint macros:
Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->blameable(); // created_by_id + updated_by_id (nullable, nullOnDelete) $table->timestamps(); });
You can also add columns individually with $table->createdBy(), $table->updatedBy() and $table->deletedBy(). Each macro returns the column definition for further chaining, and accepts an explicit column name and a referenced table (handy for cross-database schemas):
$table->createdBy('author_id', 'reporting.users');
Usage
Pick the trait that fits the model:
use Bernskiold\LaravelBlame\Concerns\Blameable; // created + updated use Bernskiold\LaravelBlame\Concerns\TracksCreatedBy; // creator only use Bernskiold\LaravelBlame\Concerns\TracksUpdatedBy; // updater only use Bernskiold\LaravelBlame\Concerns\TracksDeletedBy; // soft-delete remover class Post extends Model { use Blameable; }
That gives you:
$post->created_by_id; // set once, on creation $post->updated_by_id; // set on creation and every update $post->createdBy; // BelongsTo User $post->updatedBy; // BelongsTo User
The creating user is only written when the column is empty, so you can override it explicitly. The updating user is not overwritten when no authenticated user can be resolved (for example, a queue or console process), preserving the last known editor.
Tracking the soft-delete remover
TracksDeletedBy records who soft-deleted a row in deleted_by_id and clears it again on restore. It only acts on models that also use Laravel's SoftDeletes — there's no row to annotate after a hard delete — and it is intentionally not bundled into Blameable, since most models don't soft-delete:
use Bernskiold\LaravelBlame\Concerns\TracksDeletedBy; use Illuminate\Database\Eloquent\SoftDeletes; class Post extends Model { use SoftDeletes, TracksDeletedBy; } $post->deletedBy; // BelongsTo User
Add the column with $table->deletedBy();.
Resolving the acting user
By default the acting user is auth()->id(). Override this for contexts without an authenticated user — imports, queues, scheduled commands:
use Bernskiold\LaravelBlame\Support\Blame; Blame::resolveUserIdUsing(fn () => $importJob->triggeredByUserId);
The relations point at config('auth.providers.users.model') by default; set blame.user_model to override.
Custom column names
Globally in config/blame.php, or per model with constants:
class Post extends Model { use Blameable; public const CREATED_BY_COLUMN = 'author_id'; public const UPDATED_BY_COLUMN = 'editor_id'; }
A note on model events
The creating / updating / deleting user is captured through Eloquent model events, so it works for create(), save(), update(), delete() and restore(). Mass operations that bypass model events — such as Post::query()->update([...]) or Post::query()->delete() — will not set the blame columns. Set them explicitly in those queries if you need them.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see the License File for more information.