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

pharaonic/laravel-auditable

Laravel package that adds auditable traits to Eloquent models, automatically tracking create, update, and delete events with before/after diffs, user attribution, and configurable storage so every change is recorded and easy to review.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets:

composer require pharaonic/laravel-auditable
php artisan vendor:publish --provider="Pharaonic\Laravel\Auditable\AuditableServiceProvider"
php artisan migrate

Publishing should give you a config file (config/auditable.php) and a migration for the audits table. Run php artisan config:clear afterwards if you had a cached config, since the published file will be ignored otherwise.

Look first at:

  1. The published config — it controls the audits table name, the model used to represent an audit record, and (depending on version) whether request metadata such as IP/user agent is captured.
  2. The migration — confirm the columns you need for querying (auditable_type, auditable_id, event, old_values, new_values, user_id/auditor, timestamps) and that indexes exist on the polymorphic pair.
  3. The trait namespace shipped by the package — this is what you add to models.

First use case: pick a single high-value model (e.g. Order or Invoice), add the trait, and make a change through Eloquent. You should see a row per created / updated / deleted event with the before/after payload. Render it as a timeline on a detail page before you roll the trait out broadly — that immediately tells you whether the recorded payload is shaped the way your UI needs.

use Pharaonic\Laravel\Auditable\...\Auditable; // confirm namespace from the package source

class Order extends Model
{
    use Auditable;
}

Implementation Patterns

Trait-per-model, not global. Apply the trait deliberately rather than to every model. Audit volume grows fast; a users or sessions table with writes on every login will drown the useful signal. Start with financial/state-machine models where "who changed this and when" is a compliance or support question.

Read the trail through the relation, not raw queries. The audit model is the natural entry point — resolve auditable via the morph relation to get back to the subject, and expose a scoped accessor on your domain model:

// On the model using the trait
public function timeline()
{
    return $this->morphMany(Audit::class, 'auditable')->latest();
}

This keeps controllers thin and lets you constrain by event, user_id, or date range in one place.

Auditor resolution is the piece you will customize. Most implementations resolve the actor from auth()->user() at write time. That is fine for HTTP, but it silently records null in queued jobs, Artisan commands, seeders, and webhook handlers. If those paths matter, wrap them so the actor is set explicitly, or add your own resolution logic (a resolving/boot hook in the trait, or an app-level "current actor" singleton that your job middleware populates). For impersonation features, make sure the recorded actor is the impersonator, not the impersonated user.

Filtering the payload. You rarely want to store everything. Two levers: an allow/deny list of attributes in the config or on the model, and model-level $hidden-style exclusion. Exclude password hashes, remember tokens, API secrets, and large JSON blobs — the audits table is read far more often than it is written and a fat payload makes the timeline view unusable. Keep updated_at out of the diff too, or every row looks like a change.

Diffing for the UI. old_values and new_values are stored as JSON. Compute the changed keys in a presenter/resource layer (array_diff_assoc on the decoded arrays) rather than in Blade, and cast them to arrays on the audit model. Pair each key with a human label from your model's attribute labels so the timeline reads as "Status: pending → paid" instead of status: 1 → 2.

Bulk and transactional workflows. Audit rows are created by model events, so they participate in your transaction — a rollback removes the audit too, which is usually what you want. For importers and batch jobs, chunk your writes and consider whether you need an audit per row or one summary record; thousands of rows per import will dominate the table.

Retention. Decide early. Add a scheduled command that prunes audits older than N months (or archives them to cold storage) and index the table on (auditable_type, auditable_id) plus created_at. Without pruning, this table becomes the largest in the database within a year on a busy app.

Gotchas and Tips

  • Mass updates bypass the audit. Model::where(...)->update([...]), DB::table(...)->update(), and ->increment() do not fire model events, so nothing is recorded. The same applies to saveQuietly() and withoutEvents(). If a code path must be auditable, route it through model instances — otherwise your trail has silent holes, which is worse than no trail because people trust it.
  • Morph maps change the stored type. If you register Relation::enforceMorphMap(), existing audit rows keyed by FQCN stop resolving. Introduce the morph map before you accumulate history, or migrate the auditable_type values in the same deploy.
  • No authenticated user is not an error. Jobs, commands, and system events legitimately produce audits with a null actor. Display those as "System" rather than blank, and don't let a null actor break a ->user->name access in your view.
  • JSON column sizes. old_values/new_values should be json/longText. A truncated payload fails silently on some MySQL configurations (STRICT_TRANS_TABLES off) and you will only notice months later.
  • Deletes lose the subject. After a hard delete, auditable returns null; keep enough identifying data (a name or reference number) in the audit payload or use soft deletes on audited models if the timeline must stay readable.
  • Config caching. Any change to config/auditable.php requires php artisan config:cache (or config:clear) in deployments. This is the most common "my config change did nothing" cause.
  • 1.1.5 removed an unneeded config entry. If you published the config in an earlier version, diff the published file against the package's stub and drop the stale key. Leaving it is harmless, but a stale file hides future additions to the stub — republish to a temp location and diff rather than overwriting, so you keep your own customizations.
  • Debugging missing audits. Check, in order: trait present on the model, event actually fired (not a query-builder update), the attribute not in the exclusion list, the change being a real change (Eloquent skips the updated event if nothing is dirty), and the actor resolution path.
  • Extension points worth knowing. The audit model is a normal Eloquent model — subclass or rebind it in config if you need extra columns, accessors, or scopes. Add an observer or a created listener on the audit model itself to push records to an external log/monitoring system without touching domain code.
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk