PHPackages                             sthira-labs/version-vault - 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. [Database &amp; ORM](/categories/database)
4. /
5. sthira-labs/version-vault

ActiveLibrary[Database &amp; ORM](/categories/database)

sthira-labs/version-vault
=========================

A clean, configurable, relation-aware model versioning package for Laravel. Tracks efficient diffs (plus periodic snapshots), records changed paths and metadata, and lets you reconstruct, compare, and rollback any Eloquent model with its relations and pivot data.

v1.1.0(1mo ago)05MITPHPPHP ^8.1|^8.2|^8.3

Since Mar 17Pushed 1mo agoCompare

[ Source](https://github.com/sthira-labs/version-vault)[ Packagist](https://packagist.org/packages/sthira-labs/version-vault)[ RSS](/packages/sthira-labs-version-vault/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (2)Dependencies (12)Versions (4)Used By (0)

VersionVault (sthira-labs/version-vault)
========================================

[](#versionvault-sthira-labsversion-vault)

VersionVault is a Laravel package for model versioning that is diff-first, snapshot-assisted, and relation-aware. It captures minimal diffs, supports nested relations (including pivot data), and can reconstruct or rollback historical state.

Features
--------

[](#features)

- Diff-first storage with periodic snapshots
- Nested relations (single, collection, pivot)
- Deterministic changed paths for audits and UI diffs
- Rollback support with persisted changes
- Optional created-by tracking and user relation
- Morph map friendly (stores morph alias when configured)

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 9+ (tested against Laravel 11 and 12 via orchestra/testbench)

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

[](#installation)

```
composer require sthira-labs/version-vault
```

Publish &amp; Migrate
---------------------

[](#publish--migrate)

Publish the config and migrations, then run migrations:

```
php artisan vendor:publish --provider="SthiraLabs\\VersionVault\\VersionVaultServiceProvider"
php artisan migrate
```

To publish only the config:

```
php artisan vendor:publish --provider="SthiraLabs\\VersionVault\\VersionVaultServiceProvider" --tag=config
```

To publish only the migrations:

```
php artisan vendor:publish --provider="SthiraLabs\\VersionVault\\VersionVaultServiceProvider" --tag=migrations
```

Quick Start
-----------

[](#quick-start)

```
use SthiraLabs\VersionVault\Traits\HasVersioning;

class Project extends Model
{
    use HasVersioning;

    public function tasks() { return $this->hasMany(Task::class); }

    public function versioningConfig(): array
    {
        return [
            'name',
            'tasks:title' => [
                'users:name,pivot(role)'
            ],
        ];
    }
}

$project->recordVersion('created');
$project->recordVersionIfChanged('updated');

$result = $project->reconstructVersion(2);
$historic = $result->model;
$rollback = $project->rollbackToVersion(2);
```

### Reconstruction Basics

[](#reconstruction-basics)

Reconstruction is non-destructive by default: existing loaded relations on the template model are preserved, and only attributes present in the snapshot are applied. Relations not present in the snapshot remain untouched unless you enable `attach_unloaded_relations` or `force_replace_relation`.

`reconstructVersion()` always returns a `ReconstructionResult` DTO with:

- `model` (Model): the reconstructed model instance
- `changedPaths` (array): populated only when diff paths are enabled
- `diff` (array): populated only when diff paths are enabled

When `with_diff_paths` is enabled, many-relation operations include id-scoped paths:

- `comments.added`, `comments.103.added`
- `comments.removed`, `comments.102.removed`
- `tags.attached`, `tags.7.attached`
- `tags.detached`, `tags.6.detached`

### Relation Tracking Modes

[](#relation-tracking-modes)

Each relation can be tracked in one of two modes:

- `reference` (default): only foreign keys are tracked; relation attributes are not snapshotted.
- `snapshot`: selected relation attributes (and nested relations/pivot data) are snapshotted.

Implicit rule (Option B):

- If a relation config includes `attributes`, `relations`, or `pivot`, it is treated as `snapshot`.
- Otherwise it defaults to `reference`.

To force snapshot explicitly, add `mode: snapshot` to the relation config.

#### Example: Reference Mode (FK-only)

[](#example-reference-mode-fk-only)

```
public function versioningConfig(): array
{
    return [
        'amount',
        // Reference mode: only FK tracked (no related attributes)
        'owner' => true,
        'manager' => true,
    ];
}
```

#### Example: Snapshot Mode (full relation)

[](#example-snapshot-mode-full-relation)

```
public function versioningConfig(): array
{
    return [
        'name',
        // Snapshot mode (implicit because fields are listed)
        'category:name',
        'profile:bio',
        'tasks:title',
        'tags:name,pivot(order)',
        // Or explicitly:
        'owner' => [
            'mode' => 'snapshot',
            'attributes' => ['name', 'email'],
        ],
    ];
}
```

### Reference Relations in Reconstruction

[](#reference-relations-in-reconstruction)

Reference relations are not hydrated from the snapshot. The foreign key is stored as a normal attribute, so you can eager load after reconstruction:

```
$result = $project->reconstructVersion(2);
$result->model->load('owner', 'manager');
```

If you want FK-only tracking without listing the relation, you can track the FK attribute directly:

```
public function versioningConfig(): array
{
    return [
        'name',
        'owner_id',
    ];
}
```

### Snapshot Relations in Reconstruction

[](#snapshot-relations-in-reconstruction)

Snapshot relations are hydrated from stored data (or explicitly allowed via `reconstruct_relations`):

```
$result = $project->reconstructVersion(2, [
    'reconstruct_relations' => ['category', 'tasks', 'tags'],
    'attach_unloaded_relations' => true,
]);
```

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

[](#configuration)

Config file: `config/version-vault.php`

- `snapshot_interval` (int): store a full snapshot every N versions (default 10)
- `store_empty` (bool): allow storing a version even when no changes are detected (default false)
- `debug` (bool): enable structured debug logs (default false)
- `debug_channel` (string|null): log channel to use for debug logs (default null)
- `migrations` (bool): auto-load package migrations (default true)
- `table_name` (string): versions table name
- `model` (class-string): Version model class
- `user_model` (class-string|null): model used for `Version::user()` relation (defaults to `auth.providers.users.model`)
- `reconstruct` (array): default reconstruction options
- `reconstruct.hydrate_loaded_relations_only` (bool): only hydrate relations already loaded on the template model (default true)
- `reconstruct.preserve_missing_attributes` (bool): keep template attributes not present in the snapshot (default true)
- `reconstruct.attach_unloaded_relations` (bool): build relations even when not loaded (default false)
- `reconstruct.force_replace_relation` (bool): replace relation objects rather than update in-place (default false)
- `reconstruct.with_diff_paths` (bool): include diff + changed paths in reconstruction result (default false)
- `reconstruct.reconstruct_relations` (array|null): if set, only hydrate the listed relations
- `reconstruct.prune_missing_many_relations` (bool): for loaded `hasMany`/`morphMany`/`belongsToMany`, remove items not present in target version (default true). When false, missing items are kept; if explicit removed/detached ids exist, unrelated stale items are still dropped.
- `bindings` (array): override internal services (change detector, snapshot builder, resolver, manager, etc.)

### Migration Note

[](#migration-note)

Default relation tracking is now `reference` mode to avoid creating versions when related model attributes change but the foreign key does not. If you rely on deep relation snapshots, configure the relation with fields (implicit snapshot) or set `mode: snapshot`.

Version Records (created\_by)
-----------------------------

[](#version-records-created_by)

If an authenticated user exists, `created_by` is recorded on each version. You can also pass an explicit user id as the third argument to `recordVersion`, `recordVersionIfChanged`, or `forceSnapshot` — useful from queue jobs or system actors where `Auth::id()` is not available.

```
$project->recordVersion('updated', [], $actorId);
```

The `Version` model exposes a `createdBy()` relation that resolves via `version-vault.user_model` or falls back to `auth.providers.users.model`.

Snapshot &amp; Diff Format
--------------------------

[](#snapshot--diff-format)

See `docs/NEW_NODE_FORMAT.md` for the exact snapshot/diff schema and examples.

Events
------

[](#events)

VersionVault dispatches lifecycle events you can listen to:

- `SthiraLabs\VersionVault\Events\VersionRecording`
- `SthiraLabs\VersionVault\Events\VersionRecorded`
- `SthiraLabs\VersionVault\Events\VersionReconstructed`
- `SthiraLabs\VersionVault\Events\VersionRollback`

Details and payloads are documented in `docs/EVENTS.md`.

Testing
-------

[](#testing)

```
composer test
```

Useful Commands
---------------

[](#useful-commands)

- Install package: `composer require sthira-labs/version-vault`
- Publish config: `php artisan vendor:publish --provider="SthiraLabs\\VersionVault\\VersionVaultServiceProvider" --tag=config`
- Publish migrations: `php artisan vendor:publish --provider="SthiraLabs\\VersionVault\\VersionVaultServiceProvider" --tag=migrations`
- Run migrations: `php artisan migrate`
- Run tests: `composer test`

Development
-----------

[](#development)

Local dev option: use the provided docker-compose / Makefile or Laravel Sail.

License
-------

[](#license)

MIT

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance91

Actively maintained with recent releases

Popularity4

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 81.8% 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

Every ~55 days

Total

2

Last Release

42d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/76551293?v=4)[Roshan Poojary](/maintainers/RoshanPoojary)[@roshanpoojary](https://github.com/roshanpoojary)

---

Top Contributors

[![RoshanTG](https://avatars.githubusercontent.com/u/184596857?v=4)](https://github.com/RoshanTG "RoshanTG (9 commits)")[![Roshan0004](https://avatars.githubusercontent.com/u/100870154?v=4)](https://github.com/Roshan0004 "Roshan0004 (2 commits)")

---

Tags

laravelversioningAudithistory

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sthira-labs-version-vault/health.svg)

```
[![Health](https://phpackages.com/badges/sthira-labs-version-vault/health.svg)](https://phpackages.com/packages/sthira-labs-version-vault)
```

###  Alternatives

[venturecraft/revisionable

Keep a revision history for your models without thinking, created as a package for use with Laravel

2.6k6.9M53](/packages/venturecraft-revisionable)[mpociot/versionable

Allows to create Laravel 4 / 5 / 6 / 7 / 8 / 9 / 10 / 11 Model versioning and restoring

7851.3M7](/packages/mpociot-versionable)[lemaur/eloquent-publishing

207.8k1](/packages/lemaur-eloquent-publishing)

PHPackages © 2026

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