JSON to Laravel model and migration
This JSON to Laravel model generator turns a sample record into two files at once: an Eloquent model and the migration that creates its table. It is handy when you are storing data from a third-party API, importing a JSON export into MySQL, or scaffolding a model from a front-end mockup. The column types, $fillable list, attribute casts and belongsTo relations are all inferred from the values in your JSON.
Example
From the sample blog post record (with user_id, price, is_published, published_at, a nested meta object and timestamps), the tool generates this model:
class Post extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'title',
'slug',
'body',
'price',
'is_published',
'published_at',
'meta',
];
protected function casts(): array
{
return [
'price' => 'decimal:2',
'is_published' => 'boolean',
'published_at' => 'datetime',
'meta' => 'array',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}and this migration body:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug');
$table->string('body');
$table->decimal('price', 10, 2);
$table->boolean('is_published')->default(false);
$table->timestamp('published_at')->nullable();
$table->json('meta')->nullable();
$table->timestamps();
});The output uses the conventions of current Laravel releases: an anonymous migration class and the casts() method introduced in Laravel 11. On Laravel 10 or earlier, move the array into a protected $casts property instead.
How columns are inferred
| JSON value | Migration | Cast |
|---|---|---|
Integer ending in _id | foreignId()->constrained()->cascadeOnDelete() | plus a belongsTo method |
| Other integer | integer, or bigInteger above 2,147,483,647 | integer |
| Decimal number | decimal(10, 2) | decimal:2 |
true/false | boolean()->default(false) | boolean |
| ISO date / date-time | date / timestamp, nullable | date / datetime |
| Object or array | json()->nullable() | array |
| UUID string | uuid | none |
| String longer than 255 chars | text | none |
Other string / null | string / string()->nullable() | none |
Special fields
idis skipped because the migration always starts with$table->id().created_atandupdated_atbecome$table->timestamps(). Without them, the model getspublic $timestamps = false;so Eloquent does not try to write them.deleted_atadds$table->softDeletes()and theSoftDeletestrait.- A field named exactly
email,e_mailoremail_addressgets aunique()index (other names such asuser_emaildo not); apasswordfield gets thehashedcast and is added to$hidden. - camelCase keys such as
publishedAtare converted to snake_case columns, which is the Laravel convention.
Options
Model name is converted to PascalCase and pluralized for the table name (Category becomes categories). If your table does not follow that rule, fill in Table; the model then gets a protected $table property.
Review before migrating
One record cannot reveal everything. Check the string lengths, decide which columns should be nullable, and confirm the foreign keys: author_id will be linked to an authors table, which may need to be constrained('users'). Indexes other than unique on email are not guessed, so add index() to columns you filter or sort by often, such as slug or published_at. If you paste an array, the keys of all records are merged: a column becomes nullable() when any record has null for it or lacks the key, which helps catch optional fields.
Privacy
Generation happens in your browser, so records with personal data stay on your computer. Already have the SQL? Use SQL to Laravel migration instead. You can also convert data with JSON to PHP array for seeders, or create plain classes with JSON to PHP.