giacomomasseron/laravel-models-generator
Generate Eloquent models directly from an existing database with this Laravel artisan command. Supports MySQL, MariaDB, SQLite, PostgreSQL and SQLServer, plus polymorphic relations, UUID/ULID columns, enum, URI and JSON casting, and optional model factories.
1. Install and verify DB access. The generator introspects a live database (via doctrine/dbal), so it needs a working connection — not just migrations on disk.
composer require giacomomasseron/laravel-models-generator
php artisan laravel-models-generator:generate --help # see all flags before running
2. Publish the config before your first run. The published defaults are opinionated and one of them is destructive:
php artisan vendor:publish --tag="models-generator-config"
Immediately open config/models-generator.php and decide on clean_models_directory_before_generation (default true — it wipes app/Models before writing). For your first invocation, either set it to false or point path/namespace at a scratch location such as app/Models/Generated with namespace App\Models\Generated.
3. Do a single-table smoke test rather than generating the whole schema:
php artisan laravel-models-generator:generate -t=users
Read the generated file end-to-end. This one file tells you whether your type mapping, timestamps, $connection/$table properties, and relationship naming match your conventions. Adjust config, delete the file, run again. Only then run the full command.
4. First real use case — the one this tool is genuinely good at: adopting a legacy or externally-owned database (a production MySQL schema from another application, a data warehouse replica, a client's existing SQL Server) into a new Laravel codebase. You don't want to write 180 models by hand; you want a first pass that is PHPStan-clean and then hand-curate the parts that matter.
Generate to a scratch namespace, then promote. Keep the generator pointed at App\Models\Generated permanently and copy curated models into App\Models as you need them. This avoids the "regenerate and lose my accessors" problem entirely and gives you a repeatable diff when the upstream schema changes.
Schema-drift workflow. In CI or locally on a schedule:
php artisan laravel-models-generator:generate -f # models + factories
git diff --stat app/Models/Generated # what changed in the DB?
Because output is deterministic, git diff becomes a readable changelog of upstream schema changes — new columns, renamed relations, dropped tables. This is a much better use of the package than one-shot bootstrapping.
Base-class pattern for hand-written logic. If you must generate into your real App\Models, enable:
'base_files' => [
'enabled' => true,
'abstract' => true,
'generate_children_classes' => true,
],
This puts the generated, regenerable guts in App\Models\Base\* and leaves thin child classes for your accessors, scopes, and overrides. Regeneration then only touches Base/.
Configure the schema knowledge the DB can't express. A database has no idea that status is an enum, that settings is JSON with known keys, or that imageable_* is polymorphic. Supply that:
'morphs' => ['posts' => 'imageable'],
'enums_casting' => ['status' => App\Enums\Status::class],
'json_casting' => ['users' => ['settings' => ['theme', 'locale']]],
'uri_casting' => ['users' => ['website', 'avatar_url']],
'uuids' => ['users' => ['uuid']],
'ulids' => ['orders'],
'observers' => ['users' => UserObserver::class],
'table_traits' => ['users' => [HasRoles::class]],
'interfaces' => [HasMedia::class],
Multi-connection / multi-schema projects. -c= for a connection, -s= for a schema (essential on PostgreSQL, where public is rarely the only schema). Run once per connection and give each a distinct path/namespace if the model names would collide.
Per-table iteration for large schemas. On a 200-table database, generate table-by-table in a loop over a curated list (SHOW TABLES → -t= per table) so you control ordering, can review in chunks, and can skip the junk tables explicitly. It also lets you pass different config per batch.
The cleaner is the biggest footgun. clean_models_directory_before_generation => true plus path => app_path('Models') deletes your hand-written models, including ones the generator doesn't recreate (e.g. anything excluded via except). Always verify behaviour on a scratch path first, and never run the full command against a dirty working tree.
Relationships are foreign-key-driven only. Introspection sees belongsTo/hasMany from real FK constraints. On SQLite, tables created without FOREIGN KEY clauses yield zero relationships — a silent, confusing empty result. On MySQL, MyISAM tables likewise. Check SHOW CREATE TABLE before blaming the package.
primary_key_in_fillable defaults to true. That makes id mass-assignable, which is a real injection surface for any Model::create($request->all()). Set it to false unless you have a specific reason.
strict_types => true by default can immediately break callers passing loosely-typed values (e.g. "5" into an int setter). It's the right default, but if you're retrofitting into a sloppy codebase, expect a wave of TypeErrors and consider turning it off for the first generation pass.
Duplicate column names collide in enums_casting. The config is keyed by column name, not table+column — every status column across every table gets the same enum class. For per-table control you'll need manual edits or a post-generation script.
except defaults are Laravel-skeleton-specific. sessions, cache, cache_locks, jobs (in newer skeletons) aren't in the default list and will generate models you don't want. Audit the list against your actual SHOW TABLES.
exclude_factories defaults to users, jobs, job_batches, failed_jobs — so -f will happily write a factory for your users table's siblings but skip users itself, which is usually the opposite of what you want. Override it.
Generated factories are naive. They use bare Faker calls: random strings will exceed VARCHAR(20), random words will violate DB-level CHECK/ENUM constraints, unique columns get duplicates, and FK columns get IDs that don't exist. Treat factory output as a scaffold — expect to fix every factory in the tables you actually test against.
Custom timestamp columns. If the schema uses created/updated or a non-standard format, set timestamps.fields and timestamps.format; leaving null means standard created_at/updated_at. If there are no timestamps at all, the generated $timestamps assumption will cause inserts to fail — check and patch.
table_prefix exists precisely because models will otherwise be named TblUsers. Set it before the first run, not after you've reviewed 40 files.
Enum case for relationship names is controlled by relationships_name_case_type; the published config imports RelationshipsNameCaseTypeEnum — keep that use statement intact when editing, or the config file fatals. camel_case (default) means userProfile() rather than user_profile(). Pick one and be consistent with your existing codebase, or you'll get silent duplicate-relation confusion.
Nothing is generated except models and factories. No migrations, no seeders, no form requests, no policies. It's a read-only view of the schema. Don't expect it to reconcile with your existing migrations — if your migrations and your dev DB disagree, the generator reflects the DB, which may or may not be what you want.
Pin the package version. The Last release badge suggests active iteration, and generated-code shape (Laravel 11/12/13 attribute style vs. older property style) changes between versions. Regenerating after a composer update can produce a noisy diff across every file.
How can I help you explore Laravel packages today?