Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Migration Generator Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

The baseline-then-freeze workflow

This is the only workflow the package genuinely supports, and it should be run as a single timeboxed pass:

  1. Snapshot the schema from the read-only replica; export information_schema tables/columns/statistics/key_column_usage as ground truth alongside it.
  2. Inventory and classify every object: in-scope (tables, columns, PK/FK, indexes) vs. out-of-scope (views, procedures, triggers, partitions, custom types, check constraints, spatial columns, generated columns). The out-of-scope list is a deliverable in its own right — it becomes the written decision about what stays owned by the database.
  3. Freeze DDL for the duration. Any schema change on the source during generation-and-review invalidates your diff.
  4. Generate scoped into the quarantine path against the replica only.
  5. Replay on an empty database and diff the replayed result against ground truth. This is the gate, not code review alone — reading the files will not catch a missing index.
  6. Promote reviewed files into database/migrations, commit, and switch steady-state schema management to schema:dump.

Scoped generation beats one heroic run

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.

Ordering: the part that actually breaks

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:

  • Rename timestamps so parents precede children, then keep the ordering invariant forever by convention.
  • Split cycles manually: create both tables without the circular FK, then add the constraint in a later migration via Schema::table(...).
  • Or use the squash/flat option if the installed build supports it, producing one baseline migration you hand-edit into a deliberate order. A single reviewed baseline file is often easier to own than 200 timestamped ones.

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.

Hand-fix checklist after generation

Budget real time here — this is the work, the generation is the easy part:

  • Column order — often not preserved; usually cosmetic, but it makes diffs noisy forever.
  • Types — unsigned, tinyint(1) vs boolean, json vs longtext, decimal precision/scale, timestamp nullability.
  • Defaults — expression defaults (CURRENT_TIMESTAMP, now()), zero-dates, and string defaults with quotes are the classic silent corruption.
  • Enums — behavior varies by driver; verify the generated ->enum([...]) matches the actual value set, or convert to a string column plus a check.
  • Indexes — composite index column order matters and is easy to get subtly wrong; unique constraints vs unique indexes are not the same object.
  • Foreign keys — onDelete/onUpdate cascades, and whether the referenced index exists before the constraint is added.

Using the result downstream

Once promoted, the baseline is what everything else builds on:

  • CI runs php artisan migrate:fresh against a clean database — no shared dump, no drift.
  • Seeders and factories can be written against real column types.
  • ORM modeling has a contract instead of a guess.
  • The committed files double as an audit artifact and onboarding material.

The second-pass diff (with caveats)

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.

Gotchas and Tips

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.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator