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

History Laravel Package

konekt/history

Konekt History is a Laravel package for tracking changes to Eloquent models. It records create, update and delete events, letting you audit modifications and browse past versions of your records with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install: composer require konekt/history. The package follows the standard Konekt layout (Contracts, Models, Traits, a ServiceProvider), so it will blend with the rest of the konekt/* ecosystem if you already use Concord.
  2. Publish and migrate: php artisan vendor:publish --tag=config and --tag=migrations (or publish by provider), then php artisan migrate. Expect a single polymorphic history-style table plus a config file controlling the table name, the user resolver, and what gets stored.
  3. Wire a model: add the HasHistory trait (Konekt\History\Traits\HasHistory) to the model you want audited. That's the only mandatory change — the trait hooks Eloquent's created/updated/deleted events.
  4. First use case: an Order or Invoice model where you need "who changed what, when" for support and compliance. After saving a model, inspect $order->history (or $order->history()->latest()->get()) to see the recorded entries.

Start by reading the trait first — it defines the entire public surface (event names, relation name, attribute recording). Then the config file, then the migration. The README is thin compared to the code.

Implementation Patterns

  • Automatic audit trail: with the trait in place, each save()/delete() writes one row containing the historiable morph, the event (created, updated, deleted), a JSON payload of changed attributes, and the acting user. This is the 90% path — attach the trait and forget it.
  • Change-only vs. snapshot: most audit setups want the diff ($model->getDirty() / getChanges()), not the full record. Use diffs for storage economy; use full snapshots only if you need to reconstruct state without replaying the whole chain.
  • Reading history: treat it as a relation, not an ad-hoc query. $model->history()->where('event', 'updated')->latest()->paginate() is the canonical timeline query. Expose it through a policy-gated controller or an admin panel (Laravel Nova/Filament) rather than letting views hit the model directly.
  • Manual entries for non-CRUD events: things like "refund issued", "status transitioned via webhook", or "imported from CSV" don't map to updated. Record them explicitly through the package's historian / recordHistory-style API so the timeline is complete, not just a diff log.
  • User attribution: resolve the actor from auth()->user() via config; in queue jobs and console commands there is no user, so set a "system" actor or pass an explicit user ID to manual entries.
  • Conditional auditing: if only some models or some fields should be tracked, prefer a dedicated observer or a wrapper method over a global trait — the trait is unconditional per model. For field-level filtering, override the trait's attribute-list method in the model.
  • Multi-tenant: add a tenant_id (or team_id) column to the history table and scope reads by it. The polymorphic relation does not know about tenancy by default.
  • Backfill: for existing rows, write a one-off artisan command that iterates models and creates a synthetic "initial" entry — otherwise the timeline starts at deploy day and support will ask about the gap.

Gotchas and Tips

  • Query-builder writes bypass history entirely. Order::where(...)->update([...]) and insert()/upsert() never fire model events, so no rows are recorded. Same for DB::table() raw updates and bulk imports. If those paths exist in your codebase, wrap them in manual history entries or refactor to save().
  • updated_at-only saves produce noisy entries. A touch or a no-op save still fires updated. Filter out empty change sets in the trait override or in the writer so your timeline isn't 80% noise.
  • Dirty vs. original semantics. On updated, what you get is the post-save state of the dirty attributes; if you also need the "before" value, capture getOriginal() at updating time — the default updated hook gives you only the new values.
  • Soft deletes. With SoftDeletes, a delete is an update; you'll see deleted events only if the trait is ordered after / coexists correctly with the soft-delete hooks. Verify this explicitly with a test — this is the most common silent bug in audit packages.
  • JSON column portability. The payload is stored as JSON; on MySQL 5.6 or on SQLite in older Laravel versions this is a TEXT column with manual casting. Make sure the cast is registered on the history model or ->changes will come back as a string.
  • Config caching. After php artisan config:cache, changing the table name or user resolver requires re-caching. Also, changing the table name after migrations means you must rename the table yourself.
  • Uninstall / disable in tests. Auditing slows down big test suites. Provide a config flag or a withoutHistory()-style bypass and turn it off in phpunit.xml / TestCase, enabling it only in the tests that assert history behavior.
  • Growth and pruning. This table grows monotonically and is write-heavy on every model save. Add a composite index on (historiable_type, historiable_id, created_at) and schedule an archival command (move >N months to cold storage) before it becomes the largest table in the DB.
  • Extension points. Swap the bound History contract/model in a service provider to add columns or custom casts; implement the package's historiable contract on models that can't use the trait (e.g. legacy models with custom save logic). Prefer overriding the writer service over editing vendor files.
  • Naming drift risk. Given the package's low adoption (0 stars, 0 dependents), names like the trait, relation, and publish tags may differ from what you expect. Verify against the repo's source, not the README, and pin the version in composer.json — there's no community pressure to keep BC.
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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json