Laravel Packages

Difflock: Lint Laravel Migrations and Diff Your Schema

Published
Difflock: Lint Laravel Migrations and Diff Your Schema image

Difflock is a Laravel package from Rati Rukhadze for reviewing migrations against the database they will change. It reads migration source without loading or executing it, then uses the live schema and table-size metadata to flag changes that deserve a closer look before deployment.

Here is what Difflock includes:

  • Migration linting for destructive operations, column changes, indexes, foreign keys, and sensitive column names
  • Schema baselines recorded in JSON, which let you compare a database with a committed snapshot or another connection
  • A migration guard that runs checks before handing control to Laravel's migrate command
  • CI checks with exit codes for risky migrations, schema drift, and configuration failures
  • MCP tools for checking a table, a migration file, or migration source held in an agent's context

Reviewing Pending Migrations

difflock:lint analyses pending migrations by default. A migration such as this can look ordinary in a pull request:

Schema::table('orders', function (Blueprint $table) {
$table->string('channel');
$table->foreignId('customer_id')->constrained()->cascadeOnDelete();
$table->string('card_number', 32)->nullable();
$table->index('status');
});
 
Schema::table('customers', function (Blueprint $table) {
$table->dropColumn('legacy_token');
$table->renameColumn('name', 'full_name');
});

On a populated database, Difflock can flag the non-null channel column, the destructive dropColumn(), and the rename. It also reports that cascadeOnDelete() deletes child rows inside the database, which bypasses model events, observers, and soft deletes.

The package has rules for changes that static checks against an empty test database miss. change-column compares a ->change() call with the live column definition. unindexed-foreign-key checks whether the database engine adds the index a foreign key needs. redundant-index reports the certain leading-prefix case, such as adding an index on (status) when (status, created_at) already exists.

Use -v to expand the findings or --rule= to focus on one rule:

php artisan difflock:lint
php artisan difflock:lint -v
php artisan difflock:lint --rule=drop-column

When an application has no pending migrations, the command audits all migration files instead of producing an empty report. Existing projects can accept their current backlog with php artisan difflock:lint --all --accept, then use the resulting database/difflock/accepted.json file as the baseline for new findings.

Recording Schema Drift

Difflock records an observed schema rather than rebuilding an expected schema from migration files. That avoids trying to interpret migrations that contain conditionals, loops, or raw SQL.

php artisan difflock:diff --save

This writes database/difflock/schema.json, which you can commit. Later, php artisan difflock:diff compares the current connection against that file. You can also compare two configured connections:

php artisan difflock:diff --from=staging --to=production

The baseline contains schema structure, including tables, columns, indexes, defaults, and foreign keys. It does not include table rows or credentials. snapshot.defaults, snapshot.comments, and ignore.tables let you limit what is recorded. For another way to catch database issues in PostgreSQL, see Vacuum's schema linting.

CI and Migration Protection

Installing Difflock does not change php artisan migrate. The package only guards migrations when you use its own command:

php artisan difflock:migrate

It analyses pending migrations first. When findings reach the configured block level, it stops before Laravel writes to the database. --allow-risky deliberately bypasses Difflock's guard, while Laravel's --force remains the separate flag for production confirmation.

For CI, run the drift check and migration lint together:

- run: php artisan difflock:check --ci

The command exits with 0 when the check passes, 1 for drift or findings at the configured threshold, and 2 when the check cannot run. It can still analyse source-only rules without a database connection, but reports that table data was unavailable.

MCP Support

php artisan difflock:mcp starts a standalone MCP server over stdio. Its four tools provide table context, migration linting, schema-drift checks, and rule documentation.

The migration tool accepts either a file path or source text. An AI coding agent can check a migration before it writes the file, using the database's current schema and table statistics. The package also includes difflock:explain, which creates a Markdown briefing for a migration without calling a language model or any external API.

Installation

Difflock 1.0.0 requires PHP 8.3 and supports Laravel 12 and 13. It works with MySQL, MariaDB, PostgreSQL, and SQLite.

composer require heyosseus/difflock --dev
php artisan vendor:publish --tag=difflock-config
php artisan difflock:doctor

difflock:doctor reports the connection, available tables, pending migrations, registered rules, and whether the configured database role can write. A read-only role is a sensible choice when you want Difflock to inspect a production connection.

Difflock is available on GitHub.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
What We Know About Laravel 14 image

What We Know About Laravel 14

Read article
Fresh Package: Laravel Package Skeleton with Testbench, CI, and Boost Integration image

Fresh Package: Laravel Package Skeleton with Testbench, CI, and Boost Integration

Read article
Inertia DevTools Now Available for Firefox image

Inertia DevTools Now Available for Firefox

Read article
Laravel Scalpel Scans for Filesystem Intrusion Evidence image

Laravel Scalpel Scans for Filesystem Intrusion Evidence

Read article
Mercure Broadcasting in Laravel 13.32 image

Mercure Broadcasting in Laravel 13.32

Read article
Super Stack: Laravel Starter Kit With Filament and NativePHP image

Super Stack: Laravel Starter Kit With Filament and NativePHP

Read article