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.
Build vs. buy on change tracking. "Who changed this record, and when" is undifferentiated infrastructure — every Laravel team solves it the same way. This package reduces a potential multi-sprint build to a Composer require, a trait, and one migration, freeing engineering capacity for work that actually differentiates the product. The real decision is not whether to track authorship but how strict the requirement is: a support-convenience column versus a compliance-grade history log.
Scope validation before roadmap commitment — treat as blocking. The package description promises create/update/delete events with old and new values stored in a dedicated audits table. The hands-on technical evaluation describes it as a stamping utility that keeps created_by / updated_by in sync on the model itself. These are materially different products. Stamping answers "who last touched this row." A history table answers "what did this look like before, and who changed which field." Only the second satisfies most audit requirements. A short spike on a throwaway branch — create a record, update it, inspect what was actually written — must happen before this enters a roadmap commitment. Getting this wrong is expensive: retrofitting field-level history later requires a backfill we usually cannot reconstruct.
Compliance and enterprise-sales gating. Audit trails appear on nearly every enterprise security questionnaire and are a hard requirement for SOC 2, ISO 27001, HIPAA, and GDPR accountability. If a certification milestone or a specific enterprise deal is in the pipeline, this is a days-not-sprints way to close that checklist item and give a defensible answer to "prove who modified this record." That converts a sales blocker into a shipped capability — the cleanest ROI story available here.
Admin and customer-facing activity tooling. Once actor data exists at the model level, "record history" and "activity timeline" panels become presentation work rather than data-model work. For B2B products this is a recurring, high-ask feature request; having it blocked on a schema decision rather than a UI decision changes how much roadmap those requests consume. The canonical first release is an admin table column — "Created by Alice · 3 days ago" — plus a filter for updated_by = me, which is roughly an afternoon of work.
Retention, export, and deletion policy — a genuine product decision, not a technical one. Audit data grows monotonically and is meant to be immutable; that is the point. That forces explicit choices on retention windows, archival strategy, infrastructure cost, and the awkward GDPR tension of whether audit records are customer-exportable or customer-deletable. Surface this early — it touches cloud spend, privacy-policy language, and possibly contract language.
Upgrade-cadence planning. v13.0.0 is a major bump driven by Laravel 13 support (plus CI on PHP 8.5 and automated semantic releases). The package version tracks the framework major, so "upgrade Laravel" and "upgrade the audit package" are one workstream, permanently coupled in the delivery calendar. Budget it as recurring platform maintenance rather than discovering it mid-quarter. Note that "major" here is a proxy for framework support rather than a signal of breaking changes — but the coupling is real either way.
Dependency risk register. Single maintainer (Yajra), 176 stars, zero dependent packages, but a small and fully inspectable surface (~100 lines of trait). That is reasonable for a well-scoped utility, but it belongs in the vendor register — especially if we intend to treat the audit log as a compliance artifact rather than a convenience feature. Our mitigation is "we could fork it"; someone should confirm that is actually viable before we claim it.
Adopt when:
^13.0 aligns to Laravel 13 with CI on PHP 8.5; teams one major behind should pin ^12.0.2 and not chase this release. Teams further behind should evaluate whether the package supports their version at all before scoping anything.spatie/laravel-activitylog or owen-it/laravel-auditing on the handful of models where history genuinely matters (financial records, permissions, contracts).Look elsewhere when:
use statements).->update(), insert(), upsert(), increment(), saveQuietly(), and withoutEvents() all bypass Eloquent events and therefore bypass stamping — silently. If authorship must be reliable across those paths, the package's guarantees are weaker than the requirement.For executives — one paragraph. We have a recurring, expensive-to-build requirement — proving who changed a record and when — that shows up in enterprise security questionnaires, certification audits, and customer feature requests. This package delivers the core of that capability in days rather than sprints, on infrastructure we already run, with no vendor lock-in and no proprietary data store. It is a Composer package plus a small database migration, MIT-licensed, and removable if we change direction. The decision to make now is one of scope: whether we need a lightweight "who last touched this record" column or a full field-level change history. Those are different products and different costs. Approving a short technical spike lets us commit to the roadmap with the answer in hand rather than discovering the gap mid-certification.
For engineering — the summary. A trait you add to selected Eloquent models that keeps created_by / updated_by in sync, with creator / updater belongsTo relations, a configurable user resolver (the main extension point for multi-guard and tenant-scoped user models), and a small published migration. ~100 lines, single maintainer, version tracks the Laravel major — so pin ^13.0 on Laravel 13 and treat framework upgrades and package upgrades as one workstream. Three things to internalize before we adopt: (1) it stamps, it does not log history — verify on a branch before we scope compliance work against it; (2) any write that bypasses Eloquent events bypasses stamping, so grep for query-builder ->update( on trait-carrying models; (3) CLI, queue, and scheduled jobs have no authenticated user and will write null — decide once that null means "system" and render it that way in the UI so support doesn't file a bug per row. Eager-load both relations on any list view, never put the ID columns in $fillable, and backfill existing rows with a synthetic system user in a separate idempotent migration rather than guessing. Where we need real old/new values, pair this with spatie/laravel-activitylog on the specific models that warrant it rather than stretching this package past its design.
How can I help you explore Laravel packages today?