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 Process Stamps Laravel Package

orisintel/laravel-process-stamps

Laravel Process Stamps adds lightweight provenance to Eloquent models, automatically recording the command, job, request, or test that created or updated each record. Trace data changes, debug background workflows, and answer "where did this row come from?" with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require orisintel/laravel-process-stamps
    
  2. Publish config and migrations, then migrate

    php artisan vendor:publish --provider="OrisIntel\ProcessStamps\ProcessStampsServiceProvider"
    php artisan migrate
    

    This creates the dedicated stamps table (the package deliberately keeps stamps in their own table so it does not alter your existing models' schema). Publish only the config if you don't want to touch migrations:

    php artisan vendor:publish --tag=process-stamps-config
    
  3. Opt in per model by adding the trait:

    use OrisIntel\ProcessStamps\HasProcessStamps;
    
    class Order extends Model
    {
        use HasProcessStamps;
    }
    

    The trait wires up created and updated listeners for you—no explicit calls in boot() or observers needed.

Where to look first

  • config/process-stamps.php — table name, column names, and any context/formatting options.
  • The published migration — confirms the schema you'll query against.
  • src/HasProcessStamps.php — shows the relation name (typically processStamps()) and the exact events it hooks.

First use case: prove it works, then surface it

Create or update a model from php artisan tinker and inspect the resulting stamp:

$order = Order::create([...]);
$order->processStamps()->latest()->first();
// type => "console", name => "tinker", id => null (or similar)

Then make it user-visible in an admin screen ("Last modified by nightly:reconcile") or in support tooling—that is the primary day-to-day payoff.

Implementation Patterns

1. Per-record audit display

Read the latest stamp for any opted-in model and render it in an admin/inspector view:

$stamp = $order->processStamps()->latest('id')->first();
// $stamp->type, $stamp->name, $stamp->id, $stamp->created_at

Because it's a morphMany relation, $order->processStamps and eager loading (with('processStamps')) work as usual—so you can avoid N+1s in list views.

2. Querying by process (the "who wrote this" report)

Use it to answer operational questions without digging through logs:

// Every record stamped by a specific job
Order::whereHas('processStamps', fn ($q) => $q->where('name', 'ImportOrdersJob'))->get();

// Anything touched today by a scheduled task
ProcessStamp::where('type', 'schedule')
    ->whereDate('created_at', today())
    ->with('stampable')
    ->get();

This is the pattern that pays for the package: correlating a bad batch of writes back to the process that produced it.

3. Queue jobs and console commands

No integration code is required—the package resolves the current process context at write time. A job that updates 10,000 orders will stamp every row with that job's name, so a single query tells you which job is responsible for a corrupted import. Scheduled commands are captured distinctly, which is invaluable when a nightly task and a user action both mutate the same table.

4. Combining with optimistic/idempotent flows

Pair stamps with your own updateOrCreate / firstOrCreate paths. The package records the process, not user identity—so if you need "which user," keep your own updated_by column and let the package cover the process dimension (job vs. request vs. scheduler). The two are complementary, not redundant.

5. Customising process names

If you want more descriptive names than the raw class/command name (e.g. "checkout-retry" instead of the job FQCN), the config and/or trait expose a place to override the resolved name—check HasProcessStamps for the resolution hook before hand-rolling context setting.

Gotchas and Tips

  • Bulk writes bypass stamps. Model::insert(), query-builder update(), and raw SQL do not fire Eloquent events, so no stamp is recorded. If a path is "important enough to trace," route it through Eloquent (save, updateOrCreate, firstOrCreate) so the created/updated events fire.
  • No-op saves don't stamp. ->save() on an unchanged model does not fire updated, so you won't get a stamp row for it. That's usually what you want—but don't be surprised when a "save" button click produces no record.
  • Config caching. After editing config/process-stamps.php in a cached config environment, run php artisan config:clear (and redeploy). Changed table/column names otherwise silently keep the old values.
  • The stamps table grows one row per create/update. For write-heavy models this is the main cost. Schedule a prune (e.g. delete stamps older than N days) and consider whether every model needs the trait—opt in per model rather than applying it globally.
  • Testing. Since stamps are written on created/updated, factories and fixtures will produce them. Either assert against them intentionally or wrap setup in withoutEvents() / DB::transaction cleanup. Use RefreshDatabase so the stamps table doesn't accumulate across runs.
  • Queue worker context. If a job dispatches another job or uses Bus::batch, the stamp reflects the process executing the write—verify with a small test that the job name (not the dispatching request) is captured. Context can also be lost if you swallow exceptions or run code outside the queue lifecycle.
  • Trait interactions. If you already hook created/updated via observers, model events, or other traits, double-check ordering—both will run, but one may assume the other hasn't mutated the row. Stamps are additive; they don't replace your own updated_by/created_by columns.
  • Migration timing. The table is separate from your models, so the migration is non-destructive to existing tables and safe to run on a live DB—but on very large, high-churn tables the ongoing insert overhead (see above) is worth measuring before a broad rollout.
  • Extension points worth knowing. The config's column names let you remap to fit an existing audit convention; the trait is the natural override point if you need custom process-type/name resolution. Read the trait's boot method once—it's short and tells you exactly which events and relations power everything.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource