Download the PHP package williamug/audited without Composer
On this page you can find all versions of the php package williamug/audited. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download williamug/audited
More information about williamug/audited
Files in williamug/audited
Package audited
Short Description The only Laravel audit package that ships a complete admin UI — Livewire table, Vue/Inertia table, and per-model timeline — with zero configuration. A simple, robust audit logging package for Laravel applications. Drop one trait onto a model and every create, update, delete, and many-to-many relationship change is automatically recorded.
License MIT
Informations about the package audited
Audited
"The only Laravel audit package that ships a complete admin UI — Livewire table, Vue/Inertia table, and per-model timeline — with zero configuration."
A simple, robust audit logging package for Laravel applications. Drop one trait onto a model and every create, update, delete, and many-to-many relationship change is automatically recorded. Authentication events, manual logging, per-model subject relationships, request-level tracing, async queue support, scheduled pruning, and a configurable schema are included out of the box.
Table of Contents
- Requirements
- Installation
- Configuration
- Basic Usage
- Automatic Model Logging with the Auditable Trait
- Customising the Log Description
- Excluding Fields from Logs
- Manual Logging
- Facade
- Global helper
- Service class
- Suppressing Audit Logging
- System Actors (Causer)
- Authentication Event Logging
- Soft Deletes
- Many-to-Many Relationships
- The AuditAction Enum
- Querying Audit Logs
- Query Scopes
- Subject Relationship
- Audit Trail Viewer
- Global Audit Log Table
- Per-model Timeline
- Tailwind CSS setup
- Customising styles with CSS
- Publishing and fully overriding the views
- Vue and Inertia
- Self-fetch mode
- Props / Inertia mode
- Component props reference
- Extending the AuditLog Model
- Multitenancy
- Stamping Tenant Context on Every Log Entry
- Scoping Queries per Tenant
- Branch-level Isolation
- Full Example
- Queue / Async Logging
- Silent Failures
- Request Context
- Request ID
- URL and HTTP method
- Route name
- Auth guard
- Upgrading existing installs
- Pruning Old Logs
- Advanced Configuration
- Custom User Fields
- Custom Login Credential Field
- Sensitive Fields
- Custom Table Name
- Real-World Example
- Testing
- Changelog
- License
Requirements
| Dependency | Version |
|---|---|
| PHP | ^8.2 |
| Laravel | ^10.0, ^11.0, ^12.0, ^13.0 |
Installation
1. Require the package
2. Run the install command
This publishes config/audit.php and copies a timestamped migration into database/migrations/. Running the command a second time is safe — it skips files that already exist.
3. Run the migration
That's it. The package is now active. Authentication events are logged automatically and the audit:prune command is scheduled quarterly without any further setup.
Configuration
After installation, config/audit.php is in your application. Every option has a sensible default — you only need to change the values that differ from those defaults.
Basic Usage
Automatic Model Logging with the Auditable Trait
Add use Auditable; to any Eloquent model. Every created, updated, and deleted event on that model will be recorded automatically — no observers or extra setup required.
That single trait registration produces entries like:
| action | module | description |
|---|---|---|
create |
Billing |
Created Invoice #42 |
update |
Billing |
Updated Invoice #42 |
delete |
Billing |
Deleted Invoice #42 |
Update entries only record what changed. If five fields exist on the model but only one was modified, old_values and new_values will each contain that one field — not the full row.
Saving without changes produces no log entry. If save() is called with no dirty fields, nothing is written.
Customising the Log Description
Define auditLabel() on your model to replace the default ClassName #id label in log descriptions.
Log descriptions will now read:
Excluding Fields from Logs
To exclude specific fields from being recorded for a particular model, define the $auditExclude property. This is applied on top of the global sensitive_fields in the config.
Manual Logging
For events not tied to a model lifecycle — approving a report, exporting data, any custom business action — the package gives you three equivalent ways to write a log entry. Pick the one that fits your style.
Facade (recommended — no import needed, IDE-friendly):
The Audited facade is registered automatically via Laravel's package auto-discovery — no use import required in your controllers or services.
Global helper (best for quick one-liners):
The audited() helper is loaded automatically via Composer's files autoload — available everywhere in your application with no imports.
Service class (explicit, useful when injecting or mocking):
All three call the same underlying logic and write to the same table. The Facade and helper both support the full set of named arguments:
Using a custom action string
The $action parameter accepts both the AuditAction enum and a plain string. Use a plain string for any domain-specific action that is not in the enum — there is no need to extend it.
Custom action strings are stored verbatim in the action column. Handle them gracefully in your UI:
Attaching tags to a log entry
Pass a tags array to attach arbitrary key-value metadata to any log entry. Useful for batch IDs, import sources, workflow step names, or any context that does not belong in the description.
Tags are stored as JSON and cast to an array on retrieval:
Linking a manual log entry to a specific model
Pass a subject to associate the log entry with an Eloquent model. This populates subject_type and subject_id so the entry appears in $model->auditLogs() alongside the automatically generated entries.
Specifying the actor
By default the log reads auth()->user() as the actor. Pass an explicit causer when you need to attribute the log entry to a different user, or to a system process with no user at all.
For system actors (jobs, commands, scheduled tasks) see System Actors (Causer).
Suppressing Audit Logging
Use withoutAudit() to run a block of code without generating any log entries for that model class. This is useful for bulk imports, seeders, and test factories where the individual operations are not meaningful audit events.
withoutAudit() only suppresses the model class it is called on. Other auditable models used inside the same callback are logged normally:
Logging is always re-enabled after the callback, even if the callback throws an exception.
System Actors (Causer)
When logging from a job, command, or scheduled task there is no authenticated user. Without explicit attribution user_id is null, which is ambiguous — was this a guest action or a background process?
The causer parameter solves this. It accepts any Eloquent user model (same as always), a SystemCauser, or anything that implements the Causer interface.
The causer_type column makes the actor unambiguous in every log entry:
user_id |
user_name |
causer_type |
Meaning |
|---|---|---|---|
42 |
Jane Doe |
user |
Authenticated human |
null |
ImportMembersJob |
job |
Queued job |
null |
sessions:prune |
command |
Artisan command |
null |
NightlyReportGenerator |
system |
Scheduled process |
null |
null |
null |
No actor (e.g. failed login attempt) |
Custom causer type
Implement the Causer interface on any class:
Authentication Event Logging
When log_auth_events is true in the config (the default), the package automatically listens for Laravel's Login, Logout, and Failed auth events and writes a log entry for each.
| Event | Action recorded |
|---|---|
Illuminate\Auth\Events\Login |
login |
Illuminate\Auth\Events\Logout |
logout |
Illuminate\Auth\Events\Failed |
failed_login |
To disable this behaviour:
Custom login credential field
If your app identifies users by something other than email (for example, phone_number or username), configure the field used to identify subjects in failed login entries:
Soft Deletes
Models that use Laravel's SoftDeletes trait get full lifecycle coverage automatically — no extra configuration required.
| Event | Action recorded | Description |
|---|---|---|
delete() |
delete |
Deleted Invoice #42 |
restore() |
restore |
Restored Invoice #42 |
forceDelete() |
force_delete |
Permanently deleted Invoice #42 |
The restore() event produces a clean restore log entry. The intermediate updated event that Laravel fires internally during the restore operation is automatically suppressed so you do not see a spurious deleted_at change in the log.
Many-to-Many Relationships
Changes to BelongsToMany pivot tables are not recorded by default — pivot operations (attach, detach, sync, updateExistingPivot) bypass Eloquent's standard model events and would otherwise go untracked.
The Auditable trait intercepts these operations transparently through a custom relation class. Opt in per model by declaring an $auditRelationships array with the names of the relationships you want to track.
Opting in
No other changes are needed. All four pivot operations are now automatically logged for the permissions relationship:
| Operation | Action recorded | old_values |
new_values |
|---|---|---|---|
attach($ids) |
update |
null |
['permissions' => [1, 2]] |
detach($ids) |
update |
['permissions' => [1, 2]] |
null |
sync($ids) |
update × 2 |
detach entry + attach entry | (see above) |
updateExistingPivot($id, $attrs) |
update |
null |
['permissions' => [1]] |
sync() produces two log entries when IDs change — one for the detached IDs and one for the newly attached IDs. When the synced set is identical to the current set, no entries are written.
What it looks like
Multiple relationships
List every relationship you want audited:
Relationships not in the list are silently skipped even if attach() or detach() is called on them.
Suppressing pivot logs
withoutAudit() suppresses pivot log entries for the model class in the same way it suppresses create/update/delete entries:
The AuditAction Enum
Williamug\Audited\Enums\AuditAction provides a standard set of actions that covers most applications. Each case has a label() method for display text and a badgeColor() method for Tailwind CSS badge classes.
Use these directly in Blade views to render consistent action badges:
Querying Audit Logs
The old_values and new_values columns are automatically cast to arrays. You can query AuditLog directly or use the named scopes described below.
Query Scopes
All scopes are chainable and work with any other Eloquent query methods.
| Scope | Description |
|---|---|
forUser($user) |
Filter by a User model instance or a raw user ID integer |
forModule(string $module) |
Filter by module name |
withAction(AuditAction\|string $action) |
Filter by action — accepts the enum or a plain string |
between($from, $to) |
Filter by created_at date range — accepts Carbon instances, strings, or timestamps |
forSubject(Model $subject) |
Filter by a specific Eloquent model instance |
Subject Relationship
Every log entry written by the Auditable trait stores a polymorphic link back to the model that was acted on (subject_type and subject_id columns). This link is also populated when you pass subject: to ActivityLogService::log() manually.
Querying from the model:
Resolving the subject from a log entry:
Existing apps: see Upgrading existing installs for the migration that adds all new columns at once.
Audit Trail Viewer
The package ships with two ready-made viewer components:
- Global audit log table —
<livewire:audited.log-table />— an admin dashboard showing all entries across the entire application, with live search, filters, and expandable detail rows. - Per-model timeline —
<x-audited::timeline :subject="$model" />— a scoped history viewer for a single record, shown inline on a show/detail page.
Global Audit Log Table
A full-featured, live-filtered table for an audit log admin page. Drop one tag and it works:
That single tag renders:
- Live search — matches user name, description, and IP address as you type
- Action filter — dropdown auto-populated from the actions in your logs
- Module filter — dropdown auto-populated from the modules in your logs
- Level filter — filter by user role/level
- Platform filter — Web, Mobile, or CLI
- Date range — from/to date pickers
- Clear Filters — appears only when a filter is active
- Paginated table — Date & Time · User · Level · Action badge · Module · Description · Platform badge · IP Address · Device
- Expandable detail row — click View on any row to see request context, old/new value diff, and tags inline
Requires Livewire 3 or 4. The component is only registered when Livewire is detected — no error if it is absent.
Per-model Timeline
A chronological history viewer scoped to one record. Drop it on any show/detail page:
A timeline is a vertical list of events ordered by time, connected by a line. Each entry shows the action badge, the module, what changed, who did it, and when.
Blade component
| Prop | Type | Default | Description |
|---|---|---|---|
subject |
Model |
required | The Eloquent model whose history to display |
limit |
int |
25 |
Maximum number of entries to show |
show-values |
bool |
false |
Render a before/after diff table for each entry |
Each rendered entry includes:
- Action badge — colour-coded by action type using the
AuditActionenum - Module — the area of the application (e.g. Billing, Users)
- Description — the human-readable summary
- Actor name — who performed the action, with a causer-type badge for system actors (job, command, etc.)
- Timestamp — relative (
2 minutes ago) with the absolute time visible on hover
Livewire component (live pagination)
If your application uses Livewire 3 or 4, a live-paginated version is available:
| Prop | Type | Default | Description |
|---|---|---|---|
subject |
Model |
required | The Eloquent model whose history to display |
per-page |
int |
10 |
Entries per page |
show-values |
bool |
false |
Render a before/after diff table for each entry |
Livewire is an optional dependency — the component is only registered when Livewire is detected. No error is thrown if Livewire is not installed.
Tailwind CSS setup
The package views use Tailwind CSS utility classes. In production, Tailwind's content scanner only looks at your application files — vendor/ is excluded by default. Add the package view path to your Tailwind config so the classes are never purged.
Tailwind v3 (tailwind.config.js):
Tailwind v4 (resources/css/app.css):
Without this, the audit views will render with no styling in any environment where Tailwind's output is built (staging, production, or any Vite/Mix build step).
Customising styles with CSS
Every meaningful element in the package views has a semantic audited-* CSS class alongside the Tailwind utilities. You can target these with plain CSS in your own stylesheet — no need to publish the views.
Timeline components (<x-audited::timeline> and <livewire:audited.timeline>):
| Class | Element |
|---|---|
audited-timeline |
Outer wrapper |
audited-timeline-entry |
Each <li> entry |
audited-timeline-connector |
Vertical connecting line |
audited-timeline-dot |
Circle dot for each entry |
audited-action-badge |
Action badge (Create, Update, etc.) |
audited-module-label |
Module chip |
audited-timeline-description |
Description text |
audited-timeline-actor |
Actor/user name line |
audited-causer-badge |
Causer-type badge (job, command, etc.) |
audited-values-diff |
Before/after diff table wrapper |
audited-values-diff-field |
Field name cell |
audited-values-diff-before |
Old value cell |
audited-values-diff-after |
New value cell |
audited-timestamp |
<time> element |
Log table (<livewire:audited.log-table>):
| Class | Element |
|---|---|
audited-log-table |
Outer wrapper |
audited-log-table-filters |
Filters card |
audited-log-table-search |
Search input |
audited-log-table-select |
Filter dropdowns |
audited-log-table-date |
Date range inputs |
audited-log-table-clear |
Clear Filters button |
audited-log-table-row |
Each table row |
audited-log-table-row--expanded |
Modifier on an expanded row |
audited-log-table-detail |
Expanded detail <tr> |
audited-log-table-detail-context |
Request context section inside detail row |
audited-log-table-detail-tags |
Tags section inside detail row |
audited-log-table-toggle |
View/Close toggle button |
audited-action-badge |
Action badge |
audited-causer-badge |
Causer-type badge |
audited-platform-badge |
Platform badge |
audited-platform-badge--web |
Web platform variant |
audited-platform-badge--mobile |
Mobile platform variant |
audited-platform-badge--cli |
CLI platform variant |
audited-values-diff |
Before/after diff table wrapper |
audited-values-diff-field |
Field name cell |
audited-values-diff-before |
Old value cell |
audited-values-diff-after |
New value cell |
Example — brand the action badge with your own colours:
No view publishing required.
Publishing and fully overriding the views
For deeper structural changes, publish and edit:
Published views live in resources/views/vendor/audited/ and are never overwritten by package updates.
Vue and Inertia
The package ships two Vue 3 components — AuditLogTable.vue and AuditTimeline.vue — that mirror the Livewire components exactly. They work in two modes automatically detected at runtime:
| Mode | When to use | How data flows |
|---|---|---|
| Self-fetch | Standalone Vue SPA, or any page where you don't want to write a controller | Component fetches JSON from the built-in API routes |
| Props / Inertia | Inertia apps — data comes from your controller | Pass a paginator as props; component emits events for filter changes |
Step 1 — Publish the Vue components
Files are copied to resources/js/vendor/audited/. Edit them freely — they follow the same audited-* CSS class conventions as the Blade/Livewire views.
Step 2 — Enable the API routes (self-fetch mode only)
Skip this step if you are using Inertia props mode.
The routes are registered with ['web', 'auth'] middleware by default. Override in config/audit.php:
Self-fetch mode (standalone Vue / SPA)
Drop a component in and it works — no controller needed:
Props / Inertia mode
Use the ServesAuditLogs trait to build the controller with one line:
The props passed to the page match what the Vue components expect (logs, allActions, allModules, allLevels, filters).
In the Vue page component, listen to filter-change and call Inertia's router.get():
For the timeline in props mode:
Component props reference
AuditLogTable.vue
| Prop | Type | Default | Description |
|---|---|---|---|
logs |
Object\|null |
null |
Paginator from auditLogProps(). If null → self-fetch mode |
allActions |
Array |
[] |
Action filter options |
allModules |
Array |
[] |
Module filter options |
allLevels |
Array |
[] |
Level filter options |
filters |
Object |
{} |
Initial filter values (Inertia mode) |
endpoint |
String |
/audited/api/logs |
API endpoint (self-fetch mode) |
Events: filter-change(filters), page-change(url)
AuditTimeline.vue
| Prop | Type | Default | Description |
|---|---|---|---|
logs |
Object\|Array\|null |
null |
Paginator or array from auditTimelineProps(). If null → self-fetch mode |
subjectType |
String |
null |
Eloquent model class (self-fetch mode) |
subjectId |
String\|Number |
null |
Model primary key (self-fetch mode) |
endpoint |
String |
/audited/api/timeline |
API endpoint (self-fetch mode) |
showValues |
Boolean |
false |
Show before/after diff table |
perPage |
Number |
10 |
Entries per page (self-fetch mode) |
Events: page-change(url)
Extending the AuditLog Model
If your application needs additional relationships or extra columns, extend the package's base model.
Step 1 — Create your extended model
Step 2 — Point the config to your model
Step 3 — Add extra columns in a separate migration
Create a new migration in your application (do not modify the package migration):
Multitenancy
The package supports single-database multitenancy with multiple branches out of the box. The column names are entirely up to your application — company_id, tenant_id, business_id, facility_id, branch_id — whatever your data model uses.
There are two sides to multitenancy: writing (stamping the tenant onto each log entry) and reading (ensuring each tenant only queries their own logs). Both are handled on your custom model.
Stamping Tenant Context on Every Log Entry
Override extraColumns() on your custom model. The package calls this method on every write and merges the returned array into the log entry automatically.
Add the corresponding columns in a migration:
Every log entry — whether written automatically by the Auditable trait, by auth event listeners, or by a manual ActivityLogService::log() call — will now include the current user's company_id and branch_id.
Scoping Queries per Tenant
Add a global scope to your custom model so tenant A can never read tenant B's logs:
Branch-level Isolation
Make the scope conditional — head-office users see all branches while branch users see only their own:
Full Example
Queue / Async Logging
By default, audit log entries are written synchronously on every model event. In high-traffic applications this adds a small database write to every request. You can move all writes to a background queue with a single config change:
Or via your .env file:
When queued, a WriteAuditLog job is dispatched instead of writing directly. No call sites change — the public API is identical. Make sure your queue worker is running:
To return to synchronous writes, set 'queue' => false (the default).
Silent Failures
By default, a failed audit log write (connection error, missing table, constraint violation) throws an exception and surfaces to the caller. In production you may prefer that audit logging never crashes a real user request:
Or via .env:
When enabled, exceptions from log writes are caught, logged to Laravel's default logger at error level, and then silently discarded. The rest of the request continues normally.
Request Context
Every log entry automatically captures full context about the request it came from. No configuration required.
| Column | Web request | API request | CLI command |
|---|---|---|---|
platform |
web |
mobile |
cli |
ip_address |
Client IP | Client IP | null |
user_agent |
Browser UA string | Client UA string | null |
url |
Full URL with query string | Full URL | null |
http_method |
GET, POST, PUT, … |
GET, POST, PUT, … |
null |
route_name |
Named route or null |
Named route or null |
null |
auth_guard |
web, api, admin, … |
web, api, admin, … |
Guard name or null |
request_id |
UUID (same for all logs in the request) | UUID | UUID (per command invocation) |
Request ID
A single HTTP request often triggers multiple audit log entries — a model update, a manual ActivityLogService::log() call, an observer firing. By default these entries look unrelated in the log table.
The request_id UUID is generated once per request using Laravel's scoped() container binding, which resets automatically between requests in long-running processes like Octane. All log entries from the same request share the same UUID:
URL and HTTP method
Route name
The named route is more stable than the URL path — routes are renamed less often than URLs change. Use it to group logs by feature:
Auth guard
In applications with multiple authentication guards (e.g. web for the admin panel, api for the mobile app), the guard column tells you which path the user came through:
Upgrading existing installs
If you are upgrading from an earlier version, add all new columns with a single migration:
Pruning Old Logs
The audit:prune command deletes log entries older than the configured retention period.
The command is automatically scheduled quarterly by the package's service provider. You do not need to add it to your application's schedule.
To disable automatic pruning, set prune_after_months to null in the config:
When prune_after_months is null and no --months flag is passed, the command warns and exits without deleting anything.
Advanced Configuration
Custom User Fields
By default the package reads name from the authenticated user as the display name, and does not record a user level. Change these to match your User model:
Custom Login Credential Field
Applications that identify users by phone number or username rather than email should configure this field so that failed login entries contain the correct identifier:
Sensitive Fields
Extend the default list of fields that are stripped before writing old_values and new_values:
Per-model exclusions can also be declared with $auditExclude on the model (see Excluding Fields from Logs).
Custom Table Name
If audit_logs conflicts with an existing table in your application, change the name before running the migration:
Real-World Example
The following shows a complete integration in a billing module. This is the full picture — model, controller action, background job, and audit history page. The only additions to your existing code are the trait, the causer, and one component tag.
The model
Every create, update, delete, restore, and forceDelete on Invoice is now automatically recorded. Nothing else is required for that coverage.
A controller action
A background job
The audit history page
That is the entire integration. The controller has no audit-specific wiring beyond the one manual log() call. The view has no query, no loop, no formatting logic. The background job identifies itself as a system actor with one line.
Testing
When writing tests that involve audit logging, use RefreshDatabase and assert against the audit_logs table directly.
To suppress audit logging during unrelated tests that create models, use withoutAudit() instead of Model::withoutEvents() — it only suppresses audit writes and leaves other observers intact:
To test queued logging, use Queue::fake():
License
The MIT License. See LICENSE for details.
All versions of audited with dependencies
illuminate/support Version ^10.0|^11.0|^12.0|^13.0
illuminate/database Version ^10.0|^11.0|^12.0|^13.0
illuminate/auth Version ^10.0|^11.0|^12.0|^13.0
illuminate/bus Version ^10.0|^11.0|^12.0|^13.0
illuminate/console Version ^10.0|^11.0|^12.0|^13.0
illuminate/http Version ^10.0|^11.0|^12.0|^13.0
illuminate/queue Version ^10.0|^11.0|^12.0|^13.0
illuminate/routing Version ^10.0|^11.0|^12.0|^13.0