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

Historytracker Laravel Package

laravel-enso/historytracker

HistoryTracker for Laravel Enso automatically records model changes, storing a versioned audit trail of created, updated, and deleted records with the user and timestamp behind each change, so you can review and roll back history from the Enso UI.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is archived, so first pin a version compatible with your Laravel release and read composer.json before installing:

composer require laravel-enso/historytracker

Then publish and run its config/migrations (tag names vary by version; check the service provider if these fail):

php artisan vendor:publish --provider="LaravelEnso\HistoryTracker\ServiceProvider"
php artisan migrate

Look first at:

  1. The published config file — this is the tracked-model registry and event allow-list.
  2. src/Traits/HistoryTracker.php — confirms which Eloquent events are hooked.
  3. src/Models/History.php (or equivalent) — confirms table columns, casts, and relation names.
  4. The migration — confirms indexes on historable_type / historable_id.

Register a model in config:

// config/historytracker.php — confirm exact keys against your installed version
return [
    'models' => [
        Company::class => [
            'label' => 'Company',
            'events' => ['created', 'updated', 'deleted'],
        ],
    ],
];

Add the trait:

use LaravelEnso\HistoryTracker\Traits\HistoryTracker;

class Company extends Model
{
    use HistoryTracker;
}

First useful case — audit edits to a business model:

$company->update(['name' => 'Acme International']);

$entry = $company->history()->latest()->first();

$entry->type;       // event name, e.g. "updated"
$entry->data;       // stored payload, usually the dirty/old attributes
$entry->created_by; // acting user id, if available

Implementation Patterns

Treat history as a read model, not business logic. Keep writes going through normal Eloquent saves so events fire, and read from $model->history() anywhere you need an audit trail.

$entries = $company->history()
    ->with('createdBy:id,name')
    ->latest()
    ->paginate(25);

Normalize the stored payload once. The package stores event data as JSON; shape differs between created/updated/deleted. Centralize presentation in a resource or presenter:

final class HistoryEntryResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'event' => $this->type,
            'changes' => $this->changes(),
            'user' => $this->createdBy?->name,
            'at' => $this->created_at->toIso8601String(),
        ];
    }

    private function changes(): array
    {
        $data = $this->data ?? [];

        return collect($data['attributes'] ?? $data)
            ->map(fn ($value, $key) => [
                'attribute' => $key,
                'old' => data_get($data, "old.{$key}"),
                'new' => $value,
            ])
            ->values()
            ->all();
    }
}

Log domain events that are not Eloquent events. Approvals, exports, impersonated changes, and pivot changes need explicit entries:

$company->history()->create([
    'type' => 'approved',
    'data' => ['approved_by' => $user->id, 'reason' => $request->reason],
    'created_by' => auth()->id(),
]);

Wrap writes and history-sensitive operations in transactions when the history record is part of the same logical unit. History inserts happen in the same connection and roll back with the parent write.

DB::transaction(function () use ($company, $payload) {
    $company->update($payload);
    // history row is written by the trait inside this transaction
});

Expose history through a policy-guarded endpoint. Don't rely on UI hiding alone:

public function history(Company $company)
{
    $this->authorize('viewHistory', $company);

    return HistoryEntryResource::collection(
        $company->history()->with('createdBy')->latest()->paginate()
    );
}

Scope for multi-tenancy. If the histories table is shared, either add a tenant column via a migration and a global scope, or always query through the parent model relation rather than the History model directly.

Plan retention early. History tables grow quickly. Schedule pruning or archival:

History::where('created_at', '<', now()->subMonths(18))->delete();

Override extension points in your app, not vendor code. Bind a custom History model/resource through the container if the package supports it, or extend the trait in an app-level trait so upgrades (or forks) remain possible.

In tests, assert rows directly. Because the trait writes side-effect rows, assert against the table rather than only the model:

$this->assertDatabaseHas('histories', [
    'historable_type' => Company::class,
    'historable_id' => $company->id,
    'type' => 'updated',
]);

Gotchas and Tips

  • Bulk operations bypass history. Model::where(...)->update(), insert(), upsert(), and raw SQL do not fire Eloquent events, so no history is recorded. Use ->each->update() or log manually.
  • Quiet saves bypass history. saveQuietly(), updateQuietly(), Model::withoutEvents(), and Event::fake() around writes suppress tracking.
  • Config cache bites. After editing the tracked-model registry run php artisan config:cache (or clear it). Otherwise new models silently produce no history.
  • Morph types are brittle. historable_type stores the model class unless you enforce a morph map. Renaming/moving a model orphans old records. Enforce Relation::enforceMorphMap() before adopting the package.
  • Nullable actor. created_by is usually auth()->id(), which is null in console commands, queues, and unauthenticated jobs. Make the column nullable and display “System” in the UI.
  • Soft deletes and force deletes. deleted may be tracked, but forceDelete() fires a different event and may not be captured. Check the trait before relying on it for compliance.
  • Sensitive data in JSON. The payload can include hidden attributes, password hashes, tokens, or personal data. Redact before saving or exclude models/attributes at the application layer.
  • JSON portability. MySQL json behaves differently from SQLite in tests (sorting, casting, empty arrays vs objects). Keep payload assertions loose.
  • Performance. Every tracked write adds an insert and serialization cost. Index (historable_type, historable_id, created_at), avoid tracking chatty models, and consider queued/async history if write volume is high.
  • Archived package risk. No upstream fixes for new Laravel versions, PHP deprecations, or security issues. Pin the version, fork if needed, and verify compatibility with your framework before adopting.
  • Enso assumptions. The package may expect Enso conventions (user model, route/UI structure). In a plain Laravel app, verify the createdBy relation and any published config references resolve to your classes.
  • Debugging missing entries. Check in order: trait is on the model, model is in config, event is in the allow-list, config cache cleared, write used an event-firing method, and no withoutEvents/quiet call is in the path.
  • Extension points. Prefer overriding the published config, the History model binding, and the trait in app code over editing vendor/. A small app-level TracksHistory trait wrapping the package trait makes future replacement easier.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console