PHPackages                             ahmed3bead/laravel-tenant-audit - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. ahmed3bead/laravel-tenant-audit

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

ahmed3bead/laravel-tenant-audit
===============================

Multi-tenant audit logging for Laravel applications

v1.0.0(3mo ago)161MITPHPPHP ^8.1

Since Apr 13Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/ahmed3bead/laravel-tenant-audit)[ Packagist](https://packagist.org/packages/ahmed3bead/laravel-tenant-audit)[ RSS](/packages/ahmed3bead-laravel-tenant-audit/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (7)Versions (2)Used By (0)

[![laravel-tenant-audit](assets/medium-cover.png)](assets/medium-cover.png)

laravel-tenant-audit
====================

[](#laravel-tenant-audit)

Multi-tenant audit logging for Laravel. Tracks model `created`, `updated`, `deleted`, `restored`, and custom business events per tenant, with full control over attribute filtering, actor resolution, and storage.

- Polymorphic actor support — Admin, Vendor, Customer, or any user model
- Per-model attribute allowlists and excludelists
- Per-model and global event toggles
- Custom business events (`approved`, `exported`, `impersonated`, …)
- Configurable column names, DB connection, and table
- Optional async queue support
- Laravel morph map aware
- Auto-pruning via `model:prune`

**Supports:** Laravel 10 / 11 / 12 / 13 · PHP 8.1+

---

Installation
------------

[](#installation)

```
composer require ahmed3bead/laravel-tenant-audit
```

### Publish and migrate

[](#publish-and-migrate)

```
# Publish config
php artisan vendor:publish --tag=tenant-audit-config

# Publish migration then run it
php artisan vendor:publish --tag=tenant-audit-migrations
php artisan migrate

# Optional: publish a TenantResolver stub
php artisan vendor:publish --tag=tenant-audit-stubs
```

---

Quick start
-----------

[](#quick-start)

Add the `Auditable` trait and `AuditableContract` interface to any model you want to track:

```
use Ahmed3bead\TenantAudit\Concerns\Auditable;
use Ahmed3bead\TenantAudit\Contracts\AuditableContract;

class Order extends Model implements AuditableContract
{
    use Auditable;
}
```

That's it. Every `created`, `updated`, `deleted`, and `restored` event is now logged to `tenant_audit_logs`.

---

Configuration
-------------

[](#configuration)

All options live in `config/tenant-audit.php`.

### Tenant resolution

[](#tenant-resolution)

Implement `TenantResolverContract` and point the config to it:

```
// app/Services/TenantResolver.php
use Ahmed3bead\TenantAudit\Contracts\TenantResolverContract;

class TenantResolver implements TenantResolverContract
{
    public function getTenantId(): int|string|null
    {
        return tenant()?->getTenantKey(); // stancl/tenancy example
    }
}
```

```
// config/tenant-audit.php
'tenant_resolver' => \App\Services\TenantResolver::class,
```

### Actor resolution — multiple user models

[](#actor-resolution--multiple-user-models)

The package stores the actor as a polymorphic pair (`user_type` + `user_id`), so Admin, Vendor, Customer, and any other user model are all supported without ambiguity.

**Default behaviour** — uses `auth()->user()` automatically. No config needed for a single-guard app.

**Multi-guard / multi-model** — set `user_resolver` to a callable:

```
// config/tenant-audit.php
'user_resolver' => fn () => match (true) {
    auth('admin')->check()    => ['type' => \App\Models\Admin::class,    'id' => auth('admin')->id()],
    auth('vendor')->check()   => ['type' => \App\Models\Vendor::class,   'id' => auth('vendor')->id()],
    auth('customer')->check() => ['type' => \App\Models\Customer::class, 'id' => auth('customer')->id()],
    default                   => null,
},
```

Retrieve the actor from a log entry:

```
$log = AuditLog::find(1);
$log->user;       // returns Admin, Vendor, Customer, etc. — fully resolved
$log->user_type;  // "App\Models\Admin"
$log->user_id;    // 42
```

### Attribute filtering

[](#attribute-filtering)

**Allowlist** — only audit specific attributes:

```
class Order extends Model implements AuditableContract
{
    use Auditable;

    // Only 'status' and 'total' appear in audit logs — everything else ignored
    protected array $auditable = ['status', 'total'];
}
```

**Excludelist** — never audit specific attributes on this model:

```
protected array $auditExclude = ['internal_notes', 'cache_key'];
```

**Global excludelist** — applies to every model, regardless of model-level settings:

```
// config/tenant-audit.php
'excluded_attributes' => [
    'password',
    'remember_token',
    'two_factor_secret',
    'two_factor_recovery_codes',
],
```

> When both `$auditable` and `$auditExclude` are set, `$auditable` wins — only allowlisted keys are evaluated.

### Event control

[](#event-control)

**Global** — disable specific events for all models:

```
// config/tenant-audit.php
'events' => [
    'created'      => true,
    'updated'      => true,
    'deleted'      => true,
    'restored'     => true,
    'forceDeleted' => false, // opt-in
],
```

**Per-model** — override the global list for a specific model:

```
class ShipmentOrder extends Model implements AuditableContract
{
    use Auditable;

    // Only log create and delete — skip updated and restored
    protected array $auditEvents = ['created', 'deleted'];
}
```

### Custom events

[](#custom-events)

Log any business action that is not an Eloquent lifecycle event:

```
// On the model instance
$order->auditEvent('approved');

$order->auditEvent('status_changed',
    oldValues: ['status' => 'pending'],
    newValues: ['status' => 'approved'],
    metadata:  ['reviewed_by' => 'compliance-team'],
);

// Via the Facade (useful when you don't have a model instance)
use Ahmed3bead\TenantAudit\Facades\TenantAudit;

TenantAudit::log('impersonated', $targetUser, metadata: ['by_admin_id' => 1]);
TenantAudit::log('exported', $report, tenantId: 'acme', metadata: ['format' => 'csv']);
```

**Strict mode** — allowlist permitted custom event names to catch typos:

```
// config/tenant-audit.php
'custom_events' => ['approved', 'rejected', 'exported', 'impersonated'],
```

Leave `custom_events` empty to allow any event name (permissive mode, the default).

---

Runtime audit control
---------------------

[](#runtime-audit-control)

### Disable on a single instance

[](#disable-on-a-single-instance)

```
// This update is not logged
$order->disableAudit()->update(['cache_key' => '...']);

// Re-enable for subsequent operations
$order->enableAudit()->update(['status' => 'active']);
```

### Disable for a block of code

[](#disable-for-a-block-of-code)

```
// Bulk seed or migration — no audit noise
Order::withoutAudit(function () {
    Order::query()->update(['synced_at' => now()]);
});

// Logging resumes normally after the closure — even if it throws
```

---

Querying audit logs
-------------------

[](#querying-audit-logs)

```
use Ahmed3bead\TenantAudit\Models\AuditLog;

// All logs for a tenant
AuditLog::forTenant('acme')->latestFirst()->get();

// All logs for a specific model instance
$order->auditLogs()->latestFirst()->get();

// All logs by a specific actor type + id
AuditLog::forUser(Admin::class, 1)->get();
AuditLog::forUser($admin)->get();  // model instance shorthand

// Filter by event
AuditLog::byEvent('approved')->forTenant('acme')->get();

// Filter by auditable model
AuditLog::forModel(Order::class, $order->id)->get();

// Ordering
AuditLog::latestFirst()->limit(50)->get();
AuditLog::oldestFirst()->get();
```

---

Database schema
---------------

[](#database-schema)

Single table: `tenant_audit_logs`

ColumnTypeNotes`id`bigintPK`tenant_id`stringnullable, indexed`user_type`stringnullable — morph type of actor`user_id`bigintnullable — actor PK`event`string(50)created / updated / deleted / restored / custom`auditable_type`stringmorph type of subject model`auditable_id`bigintsubject model PK`old_values`jsonnullable`new_values`jsonnullable`ip_address`inetnullable`user_agent`textnullable`metadata`jsonnullable — free-form extras`created_at`timestampno `updated_at`---

Swapping the AuditLog model
---------------------------

[](#swapping-the-auditlog-model)

Extend `AuditLog` and point the config to your class:

```
// app/Models/MyAuditLog.php
use Ahmed3bead\TenantAudit\Models\AuditLog;

class MyAuditLog extends AuditLog
{
    // add custom scopes, relations, accessors…
}
```

```
// config/tenant-audit.php
'model' => \App\Models\MyAuditLog::class,
```

---

Pruning old logs
----------------

[](#pruning-old-logs)

Enable pruning in config:

```
// config/tenant-audit.php
'prune_after_days' => 90,
```

Then schedule Laravel's built-in prune command:

```
// routes/console.php  (Laravel 11+)
Schedule::command('model:prune')->daily();
```

---

Async queue support
-------------------

[](#async-queue-support)

Write audit logs asynchronously to keep model events fast:

```
// config/tenant-audit.php
'queue'            => true,
'queue_connection' => 'redis',
'queue_name'       => 'audit',
```

Requires a working queue driver. The `sync` driver will process jobs inline.

---

Disable IP / User-Agent capture
-------------------------------

[](#disable-ip--user-agent-capture)

```
// config/tenant-audit.php
'capture_ip'         => false,
'capture_user_agent' => false,
```

---

Testing
-------

[](#testing)

```
composer test                  # run all tests
composer test:coverage         # run with coverage (min 80%)
composer format                # run Laravel Pint
```

---

License
-------

[](#license)

MIT — [Ahmed Abead](https://github.com/ahmed3bead)

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance81

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

102d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/98d9e0c899adb8f509d8d30bfaaaff6393df92ed0daadc384c16c201d4b8e52c?d=identicon)[ahmedm3bead](/maintainers/ahmedm3bead)

---

Top Contributors

[![AhmedMEbead](https://avatars.githubusercontent.com/u/157358238?v=4)](https://github.com/AhmedMEbead "AhmedMEbead (4 commits)")

---

Tags

laravelloggingAudittenantmulti-tenant

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/ahmed3bead-laravel-tenant-audit/health.svg)

```
[![Health](https://phpackages.com/badges/ahmed3bead-laravel-tenant-audit/health.svg)](https://phpackages.com/packages/ahmed3bead-laravel-tenant-audit)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[spatie/laravel-health

Monitor the health of a Laravel application

87912.0M177](/packages/spatie-laravel-health)[api-platform/laravel

API Platform support for Laravel

58174.6k17](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
