timwassenburg/laravel-pivot-table-generator
Generate pivot table migrations for Laravel many-to-many relationships with this package. It scaffolds the intermediate tables Eloquent needs, helping you set up and maintain consistent database schemas with less boilerplate.
Install the package in the project where you actually author models and migrations. Because it only generates migration files at development time, the pragmatic choice is to add it as a require-dev dependency — but be aware that dev-only migrations disappear on production deploys unless you commit the generated files (see Gotchas).
composer require timwassenburg/laravel-pivot-table-generator --dev
Laravel auto-discovers the service provider via composer.json extra.laravel.providers. There is no config to publish and no env vars to set. Confirm the command is registered before assuming a syntax:
php artisan list | grep -i pivot
The command scaffolds a migration for a many-to-many relationship, inferring the pivot table name and foreign key columns from the two model names you pass in. A typical first use case is bootstrapping the role_user table for a standard User ⇄ Role relationship:
php artisan make:pivot User Role # verify the exact signature via `help`
php artisan migrate
Then wire the relationship in both models:
// app/Models/User.php
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
// app/Models/Role.php
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
That is the entire happy path: generate the migration, migrate, add belongsToMany on both sides. Nothing else needs configuring, which is what makes the package useful — it replaces a small but error-prone piece of manual boilerplate.
Convention-first generation. The package is built around Laravel's implicit pivot naming: alphabetically sorted singular model names joined by an underscore. User + Role yields role_user, with foreign keys derived as user_id and role_id in the sorted order that matches the table name. Rely on this convention wherever possible — the moment you start passing custom names everywhere, you lose the main benefit of the tool.
Custom table and column names. When the convention doesn't fit (e.g., an existing legacy schema, or a pivot that also has a domain meaning like membership), pass explicit names to the command and mirror them in belongsToMany:
return $this->belongsToMany(Role::class, 'memberships')
->withPivot('joined_at', 'status')
->withTimestamps();
If the command doesn't accept the override, edit the generated migration before running php artisan migrate — the file is plain Laravel migration code and fully editable.
Pair it with a follow-up withPivot step. The generator only scaffolds the columns. If your pivot carries real data (a role_id, a joined_at, a status), add those columns by hand immediately after generation, while the migration is still unrun. Retrofitting them later requires a second migration and a data backfill, which is exactly the friction the package is meant to avoid.
Team workflow. Generate the pivot migration on a feature branch, commit the generated file to database/migrations/, and let review focus on the schema — the tool removes typing but you still want a human to confirm the table name and foreign key order match the belongsToMany declarations on both models. Add a CI lint that checks for orphaned pivot migrations (a migration referencing a model that doesn't exist) if your relationship graph is large.
Seeding and testing. Once the migration exists, use attach()/sync() in seeders and factories exactly as you would with a hand-written pivot. Because the generated migration uses standard foreign keys, you can rely on FK constraints in tests to catch accidental hard deletes of pivot rows before they reach production.
User + Role is role_user, not user_role. If belongsToMany returns an empty result or a "table does not exist" error, the first thing to check is the actual generated table name — \DB::select('SHOW TABLES LIKE \'%user%\'') or simply read the generated migration file.belongsToMany on both sides yourself. Don't expect the tool to modify User.php or Role.php.require-dev can bite you in production. Generated migrations live in your repo, so they deploy fine — but the Artisan command won't be available in production. That's the intent. Don't try to run make:pivot from a deploy hook; run it in dev and commit the result.php artisan migrate will not run if the pivot migration timestamp lands ahead of the migrations that create users or roles. Newly generated migrations always get the current timestamp; if you're bootstrapping a fresh project, generate the parent table migrations first. If you're adding a pivot to an existing project, this is normally fine.id/unsignedBigInteger case. If your models use HasUuids, inspect the generated migration and change foreignId(...)->constrained() to foreignUuid(...)->constrained() before migrating.withTimestamps()-style columns or different ordering rules. If the package doesn't expose a publish command, fork the migration stub into a local command — the generated file is short enough that maintaining your own is a low-cost hedge against the package going stale.-vvv to see whether the failure is in argument resolution (model names not matching namespace) or in file writing (permissions on database/migrations/). Nine times out of ten it's a namespace mismatch — the command resolves models against App\Models by default.composer.json ("^1.0" is fine, but avoid *) and treat any future release as requiring a manual review of the diff, since the test coverage you'd normally lean on isn't there.How can I help you explore Laravel packages today?