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

Recordkeeper Laravel Package

laraarabdev/recordkeeper

RecordKeeper adds a lightweight audit trail to your Laravel Eloquent models. It records creates, updates and deletes, shows what changed and who made the change, and keeps a searchable history your team can review without altering existing tables. Drop-in setup for new and existing apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish. Pull the package in and publish its config plus migrations before touching any model:

composer require laraarabdev/recordkeeper
php artisan vendor:publish --tag="recordkeeper-config"
php artisan vendor:publish --tag="recordkeeper-migrations"
php artisan migrate

Read the published config/recordkeeper.php first — it is the map of the whole package. The keys you care about on day one are the ones controlling which events are captured, the table/model used for storage, and whether writes go through the queue.

Where to look first. After the config, open the package's model trait (the README names it; in recent versions it is the trait you use on an Eloquent model) and the migration. The migration tells you exactly which columns you can query and index; the trait tells you which model events are hooked and what you can override per-model.

First use case. Take a single model — say Invoice — and add the trait:

use LaraArabDev\RecordKeeper\Concerns\RecordsActivity; // confirm exact namespace in README

class Invoice extends Model
{
    use RecordsActivity;
}

Then create, update, and delete a row and inspect the records table. You should see one row per event, with a subject morph, a causer (the authenticated user, if there is one), and a payload of changed attributes. That single loop — mutate a model, read the record — is the fastest way to validate that config, migrations, and auth resolution are all wired correctly.

v0.3.0 status. You are jumping from 0.1.3 to 0.3.0. That is two minor bumps in a pre-1.0 package, which by semver convention permits breaking changes. Pin the exact version in composer.json and read the changelog diff before upgrading an existing install.

Implementation Patterns

Trait-per-model, configured per-model. The intended pattern is opt-in: only models that need an audit trail get the trait. Keep per-model config in the model rather than the global config so the behaviour travels with the code:

class Invoice extends Model
{
    use RecordsActivity;

    protected array $recordExcept = ['updated_at', 'last_seen_at'];
    protected bool $recordSoftDeletes = true;
}

Check the README for the exact property names — they have moved between 0.1 and 0.3 — but the shape (include/exclude attribute lists, soft-delete toggles) is the stable idea.

Name your records. An audit trail with no human label is unreadable six months later. Wrap writes in a domain verb so the record list reads like a story:

RecordKeeper::record('invoice.approved', $invoice, ['approved_by' => $user->id]);

Use this for business events that do not map to a CRUD operation: status transitions, exports, permission grants, webhook deliveries.

Capture the causer once, globally. Do not pass the user into every call. Resolve it from the guard in a service provider or via the package's causer resolver config, and make sure it degrades gracefully for console commands and queued jobs where there is no authenticated user. For those paths, set the causer explicitly:

RecordKeeper::withCauser($systemUser)->record(...);

Queue the writes when volume grows. Synchronous recording puts an extra INSERT on every model write. Once traffic matters, switch recording to the queue — but only after you have made sure the causer and request context are serialised into the job. Losing the "who" is the classic regression when turning the queue on.

Scope by tenant if you are multi-tenant. Add your tenant key to the records table and to the global query scope in the same PR that introduces the package. Retrofitting tenancy onto an audit table that already holds other tenants' rows is painful.

Read it back through relationships. The real payoff is $invoice->records in a detail view and a morphMany on the user for "what did this person do". Build those two accessors early; they are what stakeholders actually ask for.

Test against the fake. For tests that are not about auditing, disable recording rather than asserting on it — otherwise every factory call litters the table and assertions get brittle. Keep a small dedicated suite that asserts the records you do care about.

Gotchas and Tips

Pre-1.0 means the API moves. 0.1.3 → 0.3.0 is a significant jump. Trait names, config keys, and method signatures in the audit/record space churn hard before 1.0. Read the full changelog diff, not just the release headline — this release note is only a compare link, so the diff is the documentation. Pin "laraarabdev/recordkeeper": "0.3.*" and upgrade deliberately.

Mass operations bypass model events. Model::where(...)->update(...), insert(), and raw queries will not produce records. If your domain relies on those, log them manually — the audit trail is only as complete as your discipline about avoiding bulk writes on tracked models.

Config cache. Any change to config/recordkeeper.php is invisible until you re-run php artisan config:cache in cached environments. This is the number one "the package is broken" false alarm.

Index the table before you need to. The records table is append-only and grows faster than anything else in your schema. At minimum index (subject_type, subject_id), causer_type, causer_id, event, and created_at. Add the indexes in the same migration you publish, not later.

Attribute payloads can be large and sensitive. Password hashes, tokens, and API keys end up in the diff if you are not careful. Audit the exclusion list on every model that stores credentials, and consider a global scrub in the config rather than per-model lists. Also watch column size — long text fields blow up a JSON payload quickly.

Soft deletes and force deletes are different events. Confirm which one your config captures. A restored event that never fires is a common surprise.

Queued recording loses request context. After switching to the queue, verify the causer, IP, and user agent still land in the row. Serialise explicitly into the job payload.

Timezone and casting. Records are usually written with now() and read back as strings. Cast timestamps in the record model and display in the app timezone, or your timeline view will drift.

Extension point. Rather than forking, hook the package's events or bind your own causer resolver / record model subclass in a service provider. That is the seam that survives minor upgrades — direct edits to vendor files do not.

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