emirkefi / laravel-schema-drift
Zero-config database vs. migration drift detector for Laravel.
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Why laravel-schema-drift?
In a perfect, strictly-regulated CI/CD environment with immutable infrastructure, schema drift shouldn't happen. If your database is completely locked down and every single change goes through a Laravel migration, you might not need this package.
However, in the real world, development is messy. This package serves as an early-warning system and safety net for the following common scenarios:
- The 3 AM Emergency Fix: A DBA or senior engineer manually adds a critical missing index or tweaks a column type directly in production to stop a crash, but forgets to write the backport migration the next morning.
- Shared & Legacy Databases: Your Laravel application doesn't exclusively own the database. You are sharing it with a legacy app, a data engineering team, or third-party tools that don't use Laravel migrations.
- Staging & QA Environments: Developers and QA teams often have more permissive access in staging environments to test theories. Drift detection ensures these environments haven't diverged significantly from your migration files before a production deployment.
- Auditing & Peace of Mind: A "belt-and-suspenders" approach to infrastructure. It allows you to programmatically verify that your code's understanding of the database perfectly matches reality.
If you are dealing with legacy systems, fast-moving startup environments, or just want absolute certainty that your production database matches your codebase, laravel-schema-drift catches the discrepancies before they cause a bug.
A powerful, zero-config Artisan command to detect schema drift between your live database and your Laravel migration files.
Ever wonder if someone manually tweaked a database column directly in production without writing a migration? Or if a legacy table is sitting in your database completely untracked? This package catches those discrepancies instantly, integrates seamlessly into your CI/CD pipelines, and can even generate the fix migrations for you automatically.
How It Works
Behind the scenes, the package uses a clever "shadow database" approach:
- It takes a snapshot of your live database schema.
- It spins up a temporary in-memory SQLite database (or connects to your configured shadow database) and runs all your migration files.
- It compares the two schemas and outputs a terminal table highlighting missing tables, untracked columns, nullability mismatches, type drift, default value drift, and index discrepancies.
- Instant Fix: Generate a Laravel migration with a single flag (
--fix) to bring your migrations in sync. - CI/CD Ready: Output machine-readable JSON, Markdown, or native GitHub Actions workflow annotations to fail PRs with clickable inline diffs.
Features
- Zero-Config Drift Detection: Compare live databases directly against migration files.
- Automatic Migration Generator (
--fix): Automatically generate a timestamped Laravel migration to synchronize detected drift without writing boilerplate code manually. - CI/CD & Pipeline Formats: Output structured
json, Markdown tables (markdown), or GitHub Actions workflow annotations (github). - Severity & Failure Controls: Categorizes drift by severity (
errorvswarning) with configurable thresholds (--min-severity=error|warning). - Cross-Database Type Normalization Engine: SQLite shadow databases use loose type affinity. Our built-in
TypeNormalizeraccurately maps dialect-specific column types across MySQL, PostgreSQL, SQLite, and SQL Server to canonical types (integer,bigint,boolean,decimal,string,datetime,json,binary), eliminating false-positive type mismatches (e.g. MySQLTINYINT(1)vs SQLite boolean/integer). - Smart Default Value Normalization: Strips dialect-specific default wrappers (such as Postgres casts
'val'::character varying, SQL Server((0)), MySQL bit literalsb'1', and boolean string variants) to ensure accurate default comparisons. - Custom Shadow Connections: Have migrations containing raw SQL statements, full-text indexes, GIS/spatial types, or stored procedures that fail on SQLite? Pass a real shadow connection (e.g.
--shadow-connection=mysql_testing) to run migrations against a dedicated test database. - Built-in Safety Guardrails: Prevents accidentally running shadow migrations against your target live/production connection.
- Fine-Grained Strictness Checks: Enable or disable checks for indexes, foreign keys, column types, and defaults.
- Ignore Patterns: Exclude vendor, framework, or legacy tables with wildcard support (e.g.
pma__*).
Requirements
- PHP 8.2 or higher
- Laravel 11.0, 12.0, or 13.0
- SQLite PHP extension enabled (when using the default in-memory shadow database)
Installation
You can install the package via Composer as a dev dependency:
composer require emirkefi/laravel-schema-drift --dev
Publish the configuration file (optional):
php artisan vendor:publish --tag=schema-drift-config
Usage
Basic Drift Check
Run the drift check against your default database connection:
php artisan schema:drift
Auto-Fix with Migration Generation
Automatically generate a Laravel migration to fix detected drift:
php artisan schema:drift --fix
To include destructive drop operations (e.g. dropping columns or tables in migration that do not exist in the live database):
php artisan schema:drift --fix --destructive
CI/CD & Pipeline Formats
Output machine-readable JSON:
php artisan schema:drift --format=json
Output GitHub Actions workflow annotations with inline errors/warnings:
php artisan schema:drift --format=github
Output Markdown summary table:
php artisan schema:drift --format=markdown >> $GITHUB_STEP_SUMMARY
Fail CI only on critical errors (e.g. missing tables/columns, type mismatches):
php artisan schema:drift --min-severity=error
GitHub Actions Workflow Example
Add this job step to your CI pipeline:
- name: Check Schema Drift run: php artisan schema:drift --format=github --min-severity=warning
Standalone Migration Generator Command
You can also invoke the migration generator directly:
php artisan schema:drift:generate-migration --name=sync_legacy_schema
Custom Live Connection or Migration Path
Check a specific database connection or custom migration directory:
php artisan schema:drift --connection=mysql --path=database/migrations
Custom Shadow Connection (MySQL / PostgreSQL / SQL Server)
When migrations contain engine-specific SQL or spatial indexes that SQLite doesn't support, supply a dedicated test database connection:
php artisan schema:drift --connection=mysql --shadow-connection=mysql_testing --fresh-shadow
Configuration
In config/schema-drift.php, you can customize formats, severity thresholds, shadow connections, strictness checks, and ignored tables:
return [ /* | Shadow Database Connection | Set to null for default in-memory SQLite, or specify a test connection name */ 'shadow_connection' => env('SCHEMA_DRIFT_SHADOW_CONNECTION', null), 'fresh_shadow' => env('SCHEMA_DRIFT_FRESH_SHADOW', true), /* | CI/CD & Output Settings */ 'default_format' => env('SCHEMA_DRIFT_FORMAT', 'table'), 'min_severity' => env('SCHEMA_DRIFT_MIN_SEVERITY', 'warning'), /* | Ignore system or vendor tables from drift analysis */ 'ignore_tables' => [ 'migrations', 'failed_jobs', 'job_batches', 'sessions', 'cache', 'cache_locks', 'password_reset_tokens', 'pma__*', ], /* | Strictness checks */ 'check_indexes' => true, 'check_foreign_keys' => true, 'check_types' => true, 'check_defaults' => true, ];
License
The MIT License (MIT). Please see License File for more information.
