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

yajra/laravel-auditable

Laravel Auditable automatically tracks model changes, logging who created, updated, or deleted records along with old and new values. A simple trait adds full audit trails to any Eloquent model, making it easy to see exactly what changed and when.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. This is a small, additive package — a trait on Eloquent models plus two indexed actor columns — not an auditing subsystem. It fits monoliths and conventional Laravel APIs where write paths go through Eloquent models. It does not fit event-driven architectures, CDC pipelines, warehouse exports, or SIEM ingestion, and it should not be positioned as an evidence-grade compliance system.

The critical architecture finding is a product-identity discrepancy, not a code-quality issue. The repository description promises create/update/delete events with old and new values in a dedicated audits table; the hands-on technical assessment describes the trait as stamping created_by / updated_by on the model itself. Those are materially different products:

Capability Stamping History table
"Who last touched this row" Yes Yes
Field-level before/after diffs No Yes
Reconstruct past record state No Yes
Satisfies most auditor requirements Unlikely More likely

Everything downstream — roadmap commitment, certification milestone, enterprise questionnaire answers — depends on which one this is. Treat that as a blocking gate, not a discovery task.

Integration feasibility. High. composer require with a framework-major-pinned constraint, publish config and migration, add two columns, apply the trait, eager-load two relations. The first visible use case (an admin table column "Created by Alice · 3 days ago" plus a updated_by = me filter) is afternoon-scale. Removal is equally cheap, which keeps lock-in low.

Technical risk.

  • Scope risk (highest). Mis-scoping as a history log means a compliance milestone built on "who last saved this row" and, worse, a later retrofit that requires a backfill we usually cannot reconstruct.
  • Silent coverage gaps. Query-builder ->update(), insert(), upsert(), increment(), raw SQL, and saveQuietly() / withoutEvents() all bypass the trait with no error. Authorship goes stale with zero signal.
  • No runtime kill switch. Disabling means removing the trait from models — so the blast radius is a code-review concern.
  • Single-maintainer, zero dependents. 176 stars, no third-party dependents, essentially no community corpus. Small inspectable surface (~100 lines) mitigates this technically; it does not mitigate it as a vendor-register entry.
  • Immutable-ish data with real policy weight. If it does write an audit table, that data grows monotonically and forces retention, archival, cost, GDPR-export, and deletion decisions.
  • No non-repudiation. Rows are mutable and deletable by anyone with DB access. Not tamper-evident, not WORM, no chain of custody.
  • Metadata hygiene. The listed release date (2026-03-18) is in the future relative to normal release cadence; verify against Packagist before citing it in a plan. A release feed we can't reconcile is a diligence smell, not a blocker.

Key questions to close before commitment:

  1. Does the trait write an audits row with old_values / new_values, or two columns on the model? Run the spike on a throwaway branch and look at the table.
  2. Does the requirement need state reconstruction or just actor attribution? Only the former is an audit trail in the compliance sense.
  3. What fraction of writes to the target models go through Eloquent model events vs. bulk/query-builder/queue paths? Grep ->update( and insert(.
  4. Which guards and user models must resolve? Any tenant-scoped user table or admin guard raises config work.
  5. Is attribution still required after a user is hard-deleted (nullOnDelete erases it)?
  6. Is "we could fork it" actually true — has anyone confirmed we can maintain a ~100-line trait through a Laravel major?

Integration Approach

Stack fit. Requires a current or immediately prior Laravel major; package versions track framework majors, so ^13.0 for Laravel 13 and ^12.0 for Laravel 12. CI runs PHP 8.5. Teams further behind should stay on ^12.0.2 and not chase this release. The constraint is not decorative — pin it explicitly.

Migration path.

  1. Publish config and migration once. Never --force over local edits; diff the stub on every upgrade.
  2. Write the columns deliberately rather than trusting the stub: nullable foreignId with nullOnDelete, plus an index on each.
  3. Backfill in a separate, idempotent, batched migration so it can be re-run. Existing rows get a synthetic "system" user ID, not a guess. Keep it out of the schema migration.
  4. Apply the trait to a small, documented list of models — or a shared base model — not scattered across fifty files.
  5. Eager-load creator / updater in every list view; two belongsTo per row is a textbook N+1. Serialize through an API Resource so null actors are consistent.

Compatibility work that is actually configuration, not code. Multi-guard setups (web + admin), impersonation sessions, and per-tenant user models all need the config's user model/resolver adjusted and then verified with auth()->id() assertions. Prefer composing your own trait over this one to add tenant-scoped or multi-guard resolution rather than forking.

Write-path compatibility is the real integration cost. Enumerate every path that mutates an audited model — HTTP handlers, queued jobs, scheduled commands, imports, admin tooling, state machines (publish(), approve()). Bulk paths need converting to each->update() or chunked loops only where authorship matters; do that conversion as a scoped task with test coverage, not as an open-ended refactor.

Sequencing.

  1. Spike (1–2 days): create/update/delete a model as a user, inspect the DB. Decide stamping vs. history. This gates everything.
  2. Decision gate: if history is required, adopt a real activity-log package (spatie/laravel-activitylog, owen-it/laravel-auditing) for the handful of models where diffs matter — financial records, permissions, contracts — and keep this package for the cheap indexed columns that drive filters and admin columns. If attribution suffices, proceed.
  3. Schema + backfill on a production-shaped snapshot.
  4. Coverage audit of non-Eloquent write paths, with the conversion backlog sized.
  5. UI/API surfacing (admin columns, filters, activity panel).
  6. Policy work in parallel: retention window, archival, export/deletion stance, cloud-cost estimate.
  7. Vendor-register entry with fork-viability confirmed.

Upgrades. v12 → v13 lists only Laravel 13 support, PHP 8.5 CI, and semantic release tooling — meaning "major" is now a proxy for framework support, not breaking change. Bundle the package bump into the Laravel upgrade workstream, diff the published migration stub against the live table, and regression-pass create/update/delete plus bulk operations. Do not schedule it as an independent task.

Operational Impact

Maintenance. Low absolute effort, permanently coupled cadence. Every Laravel major drags an audit-package major with it, forever. Budget as recurring platform maintenance in the delivery calendar rather than a one-time integration. The published config and migration are ours to maintain once edited; each upgrade requires a manual diff.

Support load. One predictable, recurring ticket class: "updated_by is stale / blank." Root causes are enumerable and worth documenting up front — bulk query-builder updates, queued and scheduled jobs with no authenticated user, saveQuietly(). Decide the convention once (null means "system") and render "System" in the UI instead of a blank cell, or support files one bug per affected row. The same guidance should land in the onboarding doc before the first ticket arrives.

Scaling. If it is genuinely stamping: two nullable indexed FK columns per audited table, negligible query cost, and the main scaling discipline is eager-loading. If it turns out to write an audit table: monotonic growth with no natural ceiling, which turns into a cost line, a retention policy, an archival/partitioning task, and an awkward GDPR conversation about whether audit records are customer-exportable or customer-deletable. That asymmetry is a reason to resolve the product-identity question before, not after, the schema ships — the retention conversation is much cheaper before there is data.

Failure modes to design against:

  • Silent non-capture. The failure has no error and no log line; the row simply lacks an author. Detect with a periodic null-rate check on created_by for user-initiated tables and alert on drift.
  • Attribution loss on user deletion. nullOnDelete preserves the row and erases the author. If attribution has compliance or dispute value, snapshot created_by_name / created_by_email at write time, or forbid hard deletes of users.
  • Overloaded semantics. updated_by reflects whoever last called save(), which may be an approver, a bulk job, or a support engineer. Don't let it answer approval questions — add explicit approved_by / approved_at.
  • No off switch. If the trait misbehaves in production there is no config flag; the mitigation is a feature flag that stops displaying the data and an incident playbook that includes reverting the trait.
  • Misplaced confidence. Someone will eventually treat these columns as an audit record in a customer or auditor conversation. Label them in the data dictionary and in any internal documentation as "last-touched attribution," not "audit log."

Ramp-up. Very low, and unusually so for a dependency. The public surface is two relations, one trait, one config file, and one migration; the trait source is short enough to read in full rather than search for. Plan for roughly half a day of engineer onboarding, most of it spent on the coverage-gaps list (which write paths bypass Eloquent events) rather than on the package itself. The absence of a third-party community corpus — zero dependents — means Stack Overflow will not help; the correct instruction to the team is "read src/AuditableTrait.php first," and that instruction should be written down.

Net for a TPM. Cheap to adopt, cheap to remove, low operational surface, and it converts a recurring feature request and a procurement checklist item into an afternoon of work — provided the spike confirms the data model matches the requirement. The package's risk is almost entirely concentrated in that one unresolved question, and the cost of getting it wrong is a backfill we cannot perform. Gate on the spike; then adopt for attribution, and pair with a real history-log package wherever diffs are genuinely required.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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