jaybizzle/laravel-migrations-organiser
Keeps your Laravel migrations folder tidy by automatically organising migration files into subfolders while Laravel still discovers and runs them. Works with migrate, rollback and refresh, so long-running projects stay clean without changing your workflow.
Install as a dev dependency — this is a tooling/setup package, not something that belongs in your runtime path:
composer require jaybizzle/laravel-migrations-organiser --dev
On Laravel 5.5+ the service provider is auto-discovered. On older versions, register the package's provider manually in config/app.php. Once installed, confirm the command signature before you trust docs or blog posts:
php artisan list | grep migrate
# expect something like: migrate:organise
The first real use case is a legacy project where database/migrations/ has accumulated hundreds of flat files. Run the command on a throwaway branch with a clean working tree:
git checkout -b chore/organise-migrations
php artisan migrate:organise
php artisan migrate:status # nothing should be pending
git status # review the moves before committing
The command buckets each file by the date portion of its timestamp prefix (e.g. 2023_04_11_120000_create_orders_table.php → database/migrations/2023_04_11/…) and rewrites the migration column in the migrations table to match the new relative path. That second half is the important part — the file move alone is trivial, the table rewrite is what keeps migrate:status and rollbacks sane.
One-time cleanup, then leave it installed. The common pattern is: run it once on a legacy app, commit the moves as a single rename-only commit, and keep the package around for occasional re-runs when a folder gets noisy again. There is no ongoing runtime footprint.
Team coordination workflow. The migrations table is per-database, not per-repo, so every developer's local DB (and any shared staging DB) has stale paths until the command runs there too. Two viable approaches:
migrate:organise once on their own DB.migrate:fresh --seed on every dev machine so all local state is rebuilt with the new paths.Bucket-by-period as a hygiene routine. After the initial cleanup, run the command at the start of each quarter or before a release cut so the current year/month folder exists and new migrations aren't dumped into a flat root. It's idempotent — files already in a bucket don't move again.
Pair it with schema dumping. On Laravel 8+, once the tree is organised, php artisan schema:dump collapses everything into schema/mysql-schema.sql and lets you prune old migration files entirely. Organising first makes that pruning decision far easier because you can see the eras of the app at a glance.
CI assertion. Add a job that runs migrate:fresh followed by migrate:status --pending and fails on non-empty output. This catches the classic failure where a migration file was moved by hand (or by a bad merge) without the table being updated.
Scoped migrations. Once files are bucketed, you can run subsets during debugging:
php artisan migrate --path=database/migrations/2024_01_15
Useful for re-running one day's worth of work against a partially-migrated local DB.
php artisan db:dump if you use spatie/laravel-db-snapshots, otherwise mysqldump/pg_dump). If the command dies halfway you'll have a half-organised tree and a half-rewritten migrations table.database/migrations; the migrations table stores that same string. A manual mv makes every moved migration look "not run" and a subsequent migrate will explode on duplicate tables/columns.YYYY_MM_DD_HHMMSS_ prefix (hand-rolled, renamed, or generated by odd tooling) may not bucket correctly. Test on a copy of the directory first if your project has any unusual filenames.git log --follow survives. git status may briefly show delete/add until you git add -A.migrate:rollback resolves each migration by the name in the table, so it follows the new paths fine. It breaks the moment the table and the filesystem disagree, which is the failure mode described above.vendor/jaybizzle/laravel-migrations-organiser/config/ and php artisan vendor:publish — the folder-depth/format (single YYYY_MM_DD level vs. nested year/month) is the kind of thing this package exposes, and defaults vary between versions. Verify on your installed version rather than assuming.--path directories outside database/migrations are almost certainly ignored. The command targets the default migrations directory. Custom migration paths (module packages, app/Domain/*/Database/Migrations) need to be organised separately or handled by a small custom command.migrations table already has the paths it recorded when migrations ran, and rewriting them mid-deploy is a needless risk.app/Console/Commands and adapt the date-parsing logic rather than fighting the package's config.How can I help you explore Laravel packages today?