versionable turns every Laravel model update into a stored, revertible version
Laravel Model versioning made easy
At a glance
- What is it?
- Versionable is a Laravel package that copies a model's attributes into a versions table whenever you update it, then lets you diff two versions or revert the model. It is small, MIT licensed, and its maintenance pace has slowed since April 2026.
- Who is it for?
- Versionable suits teams on Laravel that want an audit trail of model edits and a one-line revert without building a revision system themselves, especially where models are edited through forms and content workflows.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 6 months ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A copy of the old attributes lands in the versions table
Versionable is a Laravel package whose whole pitch is in one sentence: keep track of all your model changes and revert to previous versions of it. The mechanism behind that promise is deliberately simple.
Every time you update a versioned model, the package stores a new version row containing the previous attributes of that model. Timestamps and the optional soft delete timestamp are ignored, so the version rows do not fill up with noise fields that only record when the write happened.
That design has one obvious consequence and one less obvious one. The obvious one is that the versions table grows with every update, which is why the package also offers a cap on how many versions are kept per model. The less obvious one is timing: because a version holds the attributes as they were before the update, the version row and the model row describe different moments in time. Reading them together requires care, and the diff helper exists precisely to make that comparison straightforward.
The audience is application developers who want history on their Eloquent models without pulling in an event-sourced framework or an audit package that stores serialized snapshots in a separate structure.
Adding VersionableTrait is the entire integration
Integration cost is the package's main selling point. You add a trait to the models you want under version control, and that is the whole step. The README states it plainly after the class definition: that's it.
class Content extends Model {
use Mpociot\Versionable\VersionableTrait;
}So there is no separate service to register beyond the composer package's own service provider, no config file to wire up, and no event listener to attach. The trait hooks into the model's normal update path, which is why versions appear without you writing a save hook.
The public surface you get from that trait is what the rest of this article is about: relations to versions, previousVersion() and currentVersion(), revert(), diff(), createInitialVersion(), and the static initializeVersions(). Each model can also override behaviour through a small number of protected properties, which is where the next two sections come in.
One caveat about the trait approach worth stating plainly: since versioning happens inside the model, anything that updates a model through a path that bypasses Eloquent, such as a raw database update, will not produce a version. The package does not document bulk update or query builder behaviour, so treat mass updates as unversioned until you check.
Installing the package and creating the versions table
The package is distributed through Composer under the name mpociot/versionable:
composer require mpociot/versionableAfter that, the versions table needs to exist. The README offers two routes. The first runs the migration bundled in the package directly:
php artisan migrate --path=vendor/mpociot/versionable/src/migrationsThe alternative publishes the migrations into your application so you can customise them, and then you run the normal migration:
php artisan vendor:publish --provider="Mpociot\Versionable\Providers\ServiceProvider" --tag="migrations"php artisan migratePublishing is the route to pick if you want to change the versions table schema, for example to add a column or adjust the index, and the package makes that possible because it exposes the table name as a property on its own Version model.
The default storage is the table named versions, defined in Mpociot\Versionable\Version::$table. The README does not document the exact columns of that table, so read the published migration before you rely on it, and note that the migration step is not optional even when you only plan to use the trait on one model.
dontVersionFields stops last_login_at from creating history
The most common way a versioned model becomes noisy is a field that changes constantly for reasons unrelated to content. The README's example is a User model with a last_login_at attribute, and its judgement is blunt: you probably do not want a new version of the User model every time that user logs in.
The fix is a protected array property named dontVersionFields on the model, holding an array of the attribute names to skip, for example a User model that declares a protected $dontVersionFields array containing last_login_at alongside the VersionableTrait use.
There is a second, opposite problem. Laravel models often hide sensitive fields with the $hidden property, and by default that hidden state is respected, which would leave out exactly the fields you might most want in a version record. The package handles this with a versionedHiddenFields property listing the hidden fields that should still be saved into version data:
class User {
use VersionableTrait;
// Typically hidden fields
protected $hidden = ['email', 'password'];
// Save these hidden fields
protected $versionedHiddenFields = ['email', 'password'];
}Note the security dimension. A version row written before this property was added will not contain those hidden fields, and the package does not document a backfill for it, so enabling versionedHiddenFields does not retroactively add history.
Diffing and reverting are the two operations worth the dependency
Storing history is table stakes. The two operations that make Versionable useful are diff and revert, and both are one method call.
The diff method takes a version model as an argument, which defines the version to compare against, and if no version is passed it uses the current version. The result is an associative array with the attribute name as the key and the different attribute value. In practice you call diff() on previousVersion() to see what the last write changed, or call diff( $version ) on currentVersion() to compare against a version you name yourself.
Reverting is the other half. You can revert to the version prior to the currently active one, or to a specific version by id:
// Restore to the previous change
$content->previousVersion()->revert();
// Get model from a version
$oldModel = Version::find(100)->getModel();Reading a version back as a model, as the second line does, is the primitive to build audit screens on. Nothing about the package forces that use, but the fact that a version can produce a model rather than only a diff is what separates it from a plain attribute log.
The relation is a MorphMany, so $model->versions is accessible like any other Laravel relation, and polymorphic storage is what lets one versions table serve many model types.
Existing rows have no versions until you ask for one
A version is created on update() of the updated model, which means every row that already existed when you installed the package has no history behind it. The README calls this out and gives two ways to close the gap.
For a single instance, createInitialVersion() creates the initial version if no version exists:
$model->createInitialVersion();For a whole model class, Model::initializeVersions() does it across all instances of that model.
There is also an off switch for the other direction. If a specific model should not be versioned for the current request, disableVersioning() and enableVersioning() bracket the call:
$user = User::find(1);
$user->disableVersioning();
// This will not create a new version entry.
$user->update([
'some_attribute' => 'changed value'
]);That is the mechanism to reach for around imports, backfills, and batch jobs that would otherwise flood the versions table with noise. Combined with $keepOldVersions, which caps how many versions are kept per model and defaults to no limit, you have two ways to control growth. The README is explicit that the default is unlimited, and warns that depending on your application this could lead to a lot of versions.
Separate version tables per model and a slowing release pace
Versions default to the single table named versions, defined in Mpociot\Versionable\Version::$table. When you want different models to store versions in different tables, you extend that model and set your own table name:
class MyModelVersion extends Version
{
$table = 'mymodel_versions';
// ...
}Then the model that uses it points at that class with the $versionClass property:
class MyModel extends Eloquent
{
use VersionableTrait ;
protected $versionClass = MyModelVersion::class ;
// ...
}The catch, which the README states at the end of that section, is that you must create a migration for the new versions table yourself, matching the default versions table. Splitting the table also splits the retention policy, since $keepOldVersions is set per model.
On the state of the project: the last push was on 2026-04-08, which is also the date of the 4.4.8 release, preceded by 4.4.7 on 2026-02-25 and 4.4.6 on 2026-02-23. The recent history shows a package receiving steady patch work through early 2026, and nothing since April 2026. The license is MIT, and the repository root holds composer.json, phpunit.xml, a src/ directory and a tests/ directory, with Travis and Scrutinizer badges still in the README.
Editorial conclusion
Versionable suits teams on Laravel that want an audit trail of model edits and a one-line revert without building a revision system themselves, especially where models are edited through forms and content workflows. It is the wrong choice for an application that only needs row-level history for a handful of tables, since the versions table grows with every update unless you set $keepOldVersions, and it is the wrong choice for anyone on a Laravel version outside the range its composer.json supports. Verify first that composer require mpociot/versionable resolves against your Laravel version, that the migration path or the published migration creates the versions table your database user can write to, and that your existing rows are backfilled with createInitialVersion or Model::initializeVersions() before you rely on revert.
Frequently asked questions
How do I install versionable in a Laravel project?
Install it with composer require mpociot/versionable, then run the bundled migration with php artisan migrate --path=vendor/mpociot/versionable/src/migrations, or publish the migrations with php artisan vendor:publish --provider="Mpociot\Versionable\Providers\ServiceProvider" --tag="migrations" and run php artisan migrate.
How do I revert a model to an earlier version in versionable?
Call revert() on a version. You can revert to the version prior to the current one with $content->previousVersion()->revert(), or to a specific version by id with Version::find( $version_id )->revert().
How do I stop a column from creating a new version on every change?
Add a protected $dontVersionFields array to your model listing the attributes to skip. The README uses a last_login_at attribute on a User model as the example.
What happens to rows that existed before versionable was installed?
Versionable creates a version on update() of the updated model, so existing rows have no version history. You can create one for a single model with createInitialVersion(), or for every instance of a model with Model::initializeVersions().
Can versionable store versions of hidden fields?
Yes. Adding hidden fields to the protected $versionedHiddenFields property on the model means those fields are saved in the version data even when they are hidden with $hidden elsewhere in your project.
Is there a limit on how many versions versionable stores?
By default there is no limit and all versions are saved. Setting a protected $keepOldVersions property on your model caps how many versions are kept, for example 10 for the last ten versions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mpociot-versionable)Was this useful?