barryvdh/laravel-migration-generator
Generate Laravel migration files from an existing database schema. This package by Barry vd. Heuvel inspects your current tables and creates matching migrations, including indexes and foreign keys, making it easy to version-control legacy or inherited databases.
What you're actually installing. This is a one-shot, development-time scaffolding tool: it introspects a configured database connection and emits Laravel migration classes. There is no runtime component, no service provider you depend on in production, and no ongoing sync. Treat the output as the deliverable and the package as a disposable accelerator.
1. Install dev-only, pinned. The repo is archived with no upstream bug-fix path, so an unpinned ^1.0 will silently resolve to whatever is left on Packagist forever.
composer require --dev barryvdh/laravel-migration-generator --with-all-dependencies
# then pin the exact version in composer.json and commit the lock
2. Verify the doctrine/dbal resolution first. This is the single most common install failure on modern Laravel stacks — the package leans on DBAL's schema introspection, and DBAL majors move independently of Laravel majors. Confirm what actually resolved:
composer show doctrine/dbal
If the constraint conflict is unresolvable for your Laravel/PHP pairing, stop here; that is a hard gate, not a bumpable warning.
3. Add a dedicated legacy connection. Never point this at an existing application connection, and never at a primary. Use a read-only replica or a locally restored dump:
// config/database.php
'legacy' => [
'driver' => 'mysql',
'host' => env('LEGACY_DB_HOST', '127.0.0.1'),
'database' => env('LEGACY_DB_DATABASE'),
'username' => env('LEGACY_DB_USERNAME'), // read-only grant
'password' => env('LEGACY_DB_PASSWORD'),
'charset' => 'utf8mb4',
'collation'=> 'utf8mb4_unicode_ci',
'prefix' => '',
],
4. Confirm the real command name and flags — do not trust the README. Documentation and reality have drifted here between generate:migrations and migrate:generate across versions. Ask the installed build:
php artisan list | grep -i migrat
php artisan help migrate:generate
5. First use case: a quarantined first draft. Generate into a directory that is visibly not curated migration history, so no one mistakes it for reviewed work and no re-run can clobber hand-edited files:
php artisan migrate:generate --connection=legacy --path=database/migrations/imported
Open two things side by side: the generated files, and ground truth from information_schema (or mysqldump --no-data). The very first thing you're looking for is what's missing, not what's wrong — silent omissions are the failure mode that bites later.
This is the only workflow the package genuinely supports, and it should be run as a single timeboxed pass:
information_schema tables/columns/statistics/key_column_usage as ground truth alongside it.database/migrations, commit, and switch steady-state schema management to schema:dump.Hundreds of tables produce an unreviewable wall of files and long Artisan runs. Generate in batches and review as you go, using the table-scoping and ignore flags against the installed build:
# a coherent slice at a time
php artisan migrate:generate --connection=legacy \
--path=database/migrations/imported \
--tables=orders,order_lines,order_events
# or generate everything and skip the noise
php artisan migrate:generate --connection=legacy \
--path=database/migrations/imported \
--ignore=sessions,cache,jobs,failed_jobs,telescope_entries
Skipping framework-owned tables is worth doing every time — regenerating sessions/cache/jobs from a legacy DB pollutes your baseline with Laravel's own schema.
Generated migrations carry timestamps in generation order, which frequently disagrees with foreign-key dependency order, and cycles (two tables referencing each other) cannot be expressed as a linear chain at all. Practical patterns:
Schema::table(...).Watch the naming collisions too: MySQL caps identifiers at 64 characters, and legacy index/FK names frequently blow past Laravel's default name-length assumptions. Normalize names during review rather than letting a migrate:fresh fail in CI.
Budget real time here — this is the work, the generation is the easy part:
unsigned, tinyint(1) vs boolean, json vs longtext, decimal precision/scale, timestamp nullability.CURRENT_TIMESTAMP, now()), zero-dates, and string defaults with quotes are the classic silent corruption.->enum([...]) matches the actual value set, or convert to a string column plus a check.onDelete/onUpdate cascades, and whether the referenced index exists before the constraint is added.Once promoted, the baseline is what everything else builds on:
php artisan migrate:fresh against a clean database — no shared dump, no drift.A tempting pattern is "re-run the generator against staging to see what drifted, then diff the directories." It works as a snapshot comparison because the output is deterministic-ish, but it is not a schema diff tool: it will not detect changes in objects it drops, and re-running into the same path risks overwriting hand edits. If you do it, always generate to a throwaway directory and diff with your VCS:
php artisan migrate:generate --connection=legacy --path=database/migrations/snapshot-$(date +%F)
git diff --no-index database/migrations database/migrations/snapshot-2024-01-01
If this becomes a recurring need, that's the signal to move to Laravel's native schema:dump / schema-state tooling, which is maintained.
Archived means you own it. 246 stars, zero dependents, no security triage, no compatibility path for future Laravel/PHP majors. Pin the exact version, record the resolved doctrine/dbal version in the runbook, and give the dependency a documented retirement date and owner in the same PR that adds it. The one-line exit criterion: "removed from composer.json when the reviewed baseline is committed and schema:dump is in place."
Silent omission is worse than failure. The generator translates what it understands and quietly drops the rest: views, stored procedures, triggers, partitions, partial indexes, generated columns, check constraints, custom/domain types, spatial types, and vendor-specific defaults. It will not warn you. The mitigation is procedural, not technical: your information_schema inventory is the authority, the generated files are a draft.
Never run it against production. Even with require-dev, a misconfigured --connection pointed at a primary means introspection load and, worse, accidental migrate execution against live data later. Read-only replica or local dump, always. Grant a read-only user.
Re-running clobbers your hand edits. The generated output and your reviewed migrations must live in different directories. Once a file is promoted, it is hand-owned and never regenerated. If you need a new snapshot, generate to a new timestamped directory and diff.
Config quirks to check in the published config, not the README. Publish the config and read it before your first run:
php artisan vendor:publish --provider="Barryvdh\LaravelMigrationGenerator\...\ServiceProvider"
Look specifically at the migration path, file extension/format, charset and collation handling, index-name-length limits, and any clean-before-generate behavior — a flag that wipes the target directory is exactly the kind of thing you want to discover before pointing it at database/migrations.
Driver behavior differs. MySQL/MariaDB, PostgreSQL, SQLite, and SQL Server return different introspection data and render types differently. Validate against the exact engine and version you're targeting — testing on SQLite and shipping to MySQL 5.7 is a wasted afternoon.
Command-name and flag drift is real. The docs have shown both generate:migrations and migrate:generate, and flags like --only/--skip/--tables/--ignore vary by version. Always resolve empirically via php artisan help in the pinned build and paste the working invocation into the runbook.
Large schemas fail on review, not on generation. Thousands of foreign keys produce ordering failures on replay and 200 files nobody reads. Chunk with table scoping, squash where possible, and treat review capacity — roughly a day for a medium schema with a DB-savvy reviewer — as the real constraint on the plan.
Don't let it become a standard. With no upstream maintenance and no dependents, the correct posture is "adopt the output, not the dependency." If you find yourself writing tooling around it, or running it in a loop, or wiring it into CI, stop — that's the point to move schema ownership to Laravel's native dump/state mechanism and delete the dev dependency.
How can I help you explore Laravel packages today?