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 Audit Log Laravel Package

itmosoft/laravel-audit-log

A lightweight Laravel audit log package that automatically tracks Eloquent model changes. It records create, update, and delete events with old/new values, user attribution, and timestamps, making it easy to see who changed what and when. Configurable and simple to install.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify your runtime before anything else. This package requires PHP 8.3+ and Laravel 12 or 13. If you are still on Laravel 10/11 or PHP 8.2, stop here — there is no compatibility shim, and you cannot adopt this on the current LTS-ish stack. Check with:

php -v
php artisan --version

2. Install and migrate.

composer require itmosoft/laravel-audit-log
php artisan migrate

The service provider is auto-discovered and the migration is loaded from the package, so migrate is genuinely all that is needed to get a working audit table. Run it in a local environment first and inspect the schema before pushing to shared environments.

3. Publish the config immediately — do not skip this.

php artisan vendor:publish --tag=audit-log-config

Read the published config file top to bottom before writing any code. It is the package's real API surface: which model events are recorded, which attributes are excluded as sensitive, the table name, and the database connection all live here. Most "why isn't this being logged?" questions are answered in this file rather than in your models.

4. First use case — audit one model. Start with a single low-risk model (a Post, Ticket, or settings model), wire it up per the package's model registration mechanism, then make a real change through your app and verify a row appears:

php artisan tinker
>>> \App\Models\Post::first()->update(['title' => 'changed']);

Then query the history for that record using the package's subject-based lookup. Confirming create/update/delete all land correctly on one model is a 10-minute exercise that de-risks the whole rollout.

5. Second use case — manual logging. Before auditing every model, use the manual logging API to record a business event from a controller or action:

audit()->log('invoice.refunded', $invoice, ['amount' => $amount]);

(Exact facade/helper name will be in the package README — confirm it, but the shape is properties + description + subject.) This gets you value on day one without touching a single model.

Where to look first: the published config file, then the package's README for the exact trait name / facade name / query scope names, then the migration to see the indexed columns (you need to know whether subject_type/subject_id and created_at are indexed before you build reporting on top of it).

Implementation Patterns

Model auditing via opt-in registration. The package records create/update/delete on models you explicitly opt in. Do not wire this onto every model at once. A good rollout order is: money/finance models → permission and role changes → user account changes → content models. Audit tables grow fast and the noisiest models are usually the least interesting.

Tune events per model, not globally. The config lets you choose which model events are recorded. In practice you want created + deleted almost everywhere, and updated only where change history is genuinely valuable. updated is by far the highest-volume event and the main driver of table growth.

Sensitive attribute filtering is your compliance guardrail. Maintain the excluded-attributes list in config before the first production write, not after. Treat it as security configuration, not a nicety: password, remember_token, api_token, two_factor_secret, plus any PII your organisation classifies as restricted. Because it is config-driven, it should be reviewed in code review like a .env change.

Actor attribution — wire it to your real auth guards. The package associates entries with an actor (user or system). If your app has multiple guards, admin panels, or API token auth, verify the actor resolves correctly for each entry path. Entries written from console commands, scheduled jobs, and webhooks will typically have no actor — decide deliberately whether those should be attributed to a named "system" actor or left null, and be consistent, because reporting queries will filter on it.

Activity grouping for multi-model operations. When one user action touches several records (e.g. approving an order creates an invoice, updates stock, writes a note), use the activity grouping feature to tie those entries together under a single operation identifier. This is the single highest-value feature for building a readable "what happened" timeline in an admin UI — without it you get a flat list of unrelated rows.

Manual logging for domain events. Model auditing tells you what changed; manual logging tells you what happened. Use manual entries for things with no natural model delta: logins, exports, report generation, external API calls, failed payment attempts. Attach the relevant subject and a small properties array — but keep properties small and non-sensitive, since it is stored as JSON and is not subject to the sensitive-attribute filter.

Query by subject for record detail pages, by actor for accountability pages. The query API supports subject, actor, event, and date range. Two standard integrations:

  • On a record's detail page: history scoped to that subject, newest first, paginated.
  • An admin "activity" screen: filtered by actor + date range.

Build these as small repository/service classes rather than scattering queries through controllers — the underlying model is a package class and you want one place to change if the schema shifts.

Queue auditing is opt-in and situational. Enable it only if you have a real need to trace job lifecycle. It requires a queue connection and a running worker, and it writes rows on every job event — on a busy queue that can be a substantial fraction of your audit volume. On a large app, consider enabling it in staging only.

Separate database connection if audit volume is high. The configurable connection means you can point audit storage at a different database. This is worth doing on mature apps: it isolates audit write load from your primary, and it makes retention/archival policies much easier to operate.

Gotchas and Tips

The Laravel 12/13 + PHP 8.3 floor is the biggest adoption blocker. This is easy to miss in a package install and only surfaces at composer require time or, worse, in CI. Pin and communicate this before anyone plans work around it.

No migration publish tag is documented. Only --tag=audit-log-config is listed, and the migration is auto-loaded. That means you cannot easily edit the schema. If you need to change the table name or connection, set it in config before running migrate — changing it afterwards leaves you with an orphaned table and no rows.

Bulk operations bypass auditing. Model::query()->update([...]), ->delete(), raw SQL, and insert() do not fire Eloquent model events, so they produce no audit entries. This is the most common source of "the audit log is lying" incidents. If you audit a model, route its writes through model instances, or log the bulk operation manually with a description that states the scope.

Deletes lose context. An audit row for a deleted event typically captures the model's attributes at deletion time, but any $hidden/guarded nuance and relations are gone. If you need a full snapshot for regulated data, log it manually in the deleting observer before the record disappears.

Sensitive filtering is per attribute name, globally. If you exclude token, it is excluded on every audited model — including models where token is innocuous and useful in the history. Conversely, a field named secret_notes on one model is exposed unless it is on the list. Audit the actual column names across your audited models rather than assuming per-model semantics.

JSON properties in manual logs are not filtered. The sensitive-attribute exclusions apply to model attribute snapshots. Anything you pass into a manual log's properties array is stored verbatim. Review those call sites with the same rigour as the config.

Queue auditing fails quietly if the queue is unhealthy. With no running worker, job lifecycle events simply never get written — no error, just absent rows. If you enable it, add a health check or alert on the absence of recent queue audit entries. Also confirm audit writes inside a queued job don't themselves enqueue work, or you can create feedback loops.

Watch for audit writes inside audited models' own events. If the actor resolution or activity grouping touches a model that is itself audited, you can get recursive or duplicate entries. Test by updating an audited User while authenticated as that user and check you get exactly one row.

Plan retention before launch, not after. The release notes do not mention a pruning command, so retention is your responsibility (a scheduled delete where created_at < ?, or partitioning). Decide the number now — 90 days is a sensible default for most apps — and add the job at the same time as the migration. Retro-fitting a prune on a table with tens of millions of rows is a painful migration.

Debugging checklist when an entry doesn't appear: (1) is the model actually opted in? (2) is that event enabled in events config? (3) was the write a bulk query rather than a model save? (4) is the attribute excluded as sensitive (it will vanish from the snapshot, not block the row)? (5) is the audit connection pointing at a database you're actually inspecting? Check that last one first when rows "disappear" between environments.

Extension points to lean on. The package is deliberately thin. The config file is your primary lever; for anything beyond it (custom formatting for an admin timeline, additional actor resolution rules, retention), wrap the package rather than forking — a small service class that queries the audit model and maps rows to view DTOs keeps you upgradable.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager