NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Attributes for Laravel

History & audit trail

Enable attributes.history.enabled to record an old→new revision whenever an attribute is attached, updated — value or meta — or detached. It is off by default — no revision rows are written until you opt in:

ATTRIBUTES_HISTORY=true
use RoundlyConsulting\Attributes\Facades\Attributes;

Attributes::for($product)->history();        // Collection<AttributeRevision> — newest first
Attributes::for($product)->history('color'); // filtered by attribute name

$product->history('color');   // model shorthand
$product->attributeHistory(); // the underlying MorphMany relation

What a revision holds

ColumnHolds
typeRevisionType — attached, updated or detached.
nameThe attribute name.
old_value, new_valueStorage strings before and after — ciphertext for encrypted attributes.
old_value_type, new_value_typeAttributeType values before and after.
old_meta, new_metaMetadata before and after, cast to Collections.
owner_type, owner_idThe owning model (owner() morph-to relation).
created_atWhen the change happened.
use RoundlyConsulting\Attributes\Enums\RevisionType;

foreach ($product->history('color') as $revision) {
    $revision->type;            // RevisionType::Attached | Updated | Detached
    $revision->old_value;       // previous storage string, or null
    $revision->new_value;       // new storage string, or null on detach
    $revision->old_value_type;  // 'string', 'integer', ...
    $revision->new_value_type;
    $revision->old_meta;        // Collection|null
    $revision->new_meta;
    $revision->created_at;

    $revision->type->is(RevisionType::Updated); // bool
}

$product->attributeHistory('color')->limit(10)->get(); // a relation — query it freely

What is recorded

  • attached — the first write of a name through any writer, meta() included, and re-attaching a detached name.
  • updated — writing a value or meta to a name that is attached, including values re-sent by a sync.
  • detached — every removal of an attached name: forget(), forgetExcept() and the extras sync() drops, plus their model methods.

Pruning revisions

attributes:prune only touches attributes — pruning the revisions table is left to your application, for example with a scheduled query:

// routes/console.php
use Illuminate\Support\Facades\Schedule;
use RoundlyConsulting\Attributes\Models\AttributeRevision;

Schedule::call(function (): void {
    AttributeRevision::query()
        ->where('created_at', '<', now()->subYear())
        ->forceDelete();
})->weekly();

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.