Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Store JSON Data in a Database with Laravel 13 and Eloquent Casts

๐Ÿ› ๏ธ Laravel Tutorials5 min read

How to Store JSON Data in a Database with Laravel 13 and Eloquent Casts

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 7, 2026
How to Store JSON Data in a Database with Laravel 13 and Eloquent Casts

To store JSON data in a database with Laravel 13, add a json column in the migration, cast it to array (or AsArrayObject) in the Eloquent model, and read or write it like a normal PHP array. You can also filter on keys inside the JSON with the arrow syntax. This guide targets Laravel 13 and PHP 8.3 or newer.

When should you store JSON instead of columns?

Use a JSON column for flexible, loosely structured data: user preferences, feature flags, form builder answers, or metadata that differs per record. Use normal columns for data you filter, sort, join or constrain often. A JSON column cannot enforce foreign keys, and heavy querying inside it is slower than a proper indexed column.

Prerequisites

  • Laravel 13 on PHP 8.3 or newer.
  • MySQL 8, MariaDB, PostgreSQL or SQLite. All support JSON columns in current versions.

Step 1: Add the migration

Code
php artisan make:model Product -m
JavaScript
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->json('options')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

$table->json() creates a native JSON type where the database has one. Use jsonb() on PostgreSQL if you want binary storage and indexing support.

Step 2: How do you cast the column in the model?

JavaScript
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = ['name', 'options'];

    protected function casts(): array
    {
        return [
            'options' => 'array',
        ];
    }
}

With the array cast, Laravel encodes the array to JSON on save and decodes it on read. Laravel 13 models define casts in the casts() method.

Step 3: Save and read JSON data

JavaScript
$product = Product::create([
    'name' => 'Trail Backpack',
    'options' => [
        'color' => 'green',
        'sizes' => ['M', 'L'],
        'specs' => ['weight_kg' => 1.2, 'waterproof' => true],
    ],
]);

$product->options['color'];          // "green"
$product->options['specs']['weight_kg']; // 1.2

There is one common surprise. You cannot modify a single key on an array-cast attribute directly, because PHP returns a copy. This code fails with an "Indirect modification" notice:

PHP
$product->options['color'] = 'blue'; // does not work

Read the array, change it, and assign it back:

Shell
$options = $product->options;
$options['color'] = 'blue';
$product->options = $options;
$product->save();

Alternatively, cast the column to Illuminate\Database\Eloquent\Casts\AsArrayObject. It returns an object that tracks changes, so $product->options['color'] = 'blue' works and save() persists it.

Step 4: How do you query inside a JSON column?

CSS
Product::where('options->color', 'green')->get();

Product::where('options->specs->waterproof', true)->get();

Product::whereJsonContains('options->sizes', 'L')->get();

Product::whereJsonLength('options->sizes', '>', 1)->get();

Use the arrow to reach nested keys. whereJsonContains checks if an array holds a value, and whereJsonLength compares the number of items. You can update one key without loading the model:

JavaScript
Product::whereKey($product->id)->update(['options->color' => 'red']);

That form works on databases with JSON support, and Laravel builds the correct SQL for your driver.

Step 5: Validate JSON input

Always validate structure before saving user-submitted JSON. Use dot notation with wildcards for arrays.

JavaScript
$request->validate([
    'name' => ['required', 'string', 'max:255'],
    'options' => ['nullable', 'array'],
    'options.color' => ['nullable', 'string', 'max:50'],
    'options.sizes' => ['nullable', 'array'],
    'options.sizes.*' => ['string', 'in:S,M,L,XL'],
]);

How do you set default values for a JSON column?

A nullable column returns null for new rows, which forces ?? [] checks everywhere. Set a default in the migration with $table->json('options')->default('{}') on databases that allow it, or fill it in the model with an attribute accessor that merges your defaults with the stored array:

JavaScript
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function settings(): Attribute
{
    return Attribute::make(
        get: fn (?string $value) => array_replace_recursive(
            ['color' => 'gray', 'sizes' => []],
            json_decode($value ?? '{}', true) ?? []
        ),
        set: fn (array $value) => json_encode($value),
    );
}

This keeps the shape predictable while the database still stores only what was saved. Remember that a model accessor like this replaces the cast for that attribute, so do not declare both for the same column.

Is there a size or performance limit?

JSON documents are loaded as a whole. A column that grows to hundreds of kilobytes slows every query that selects it. If you only need a few keys, select them with the arrow syntax, for example Product::select('id', 'options->color as color')->get(), or move large data to a related table.

Common mistakes

  • Forgetting the cast. The attribute then comes back as a JSON string.
  • Storing JSON inside a string column and double-encoding it with json_encode() plus the cast.
  • Using JSON for data you search by often, instead of a normal indexed column.
  • Not validating the incoming structure, which lets arbitrary data into your database.

How do you verify it works?

  1. Run php artisan migrate and check that the products table has a JSON column.
  2. Create a record in php artisan tinker and inspect the raw value with DB::table('products')->first(). It should be a JSON string.
  3. Read the model again and confirm $product->options is an array.
  4. Run a where('options->color', 'green') query and make sure it returns the record.

FAQ

What is the difference between json and jsonb in Laravel?

jsonb() exists for PostgreSQL and stores a binary form that supports indexes. On other databases it behaves like json().

Can I index a JSON key?

Yes, but the method depends on the database. MySQL uses generated columns, and PostgreSQL uses GIN or expression indexes. Check your database documentation.

Should I use array or collection cast?

Use array for plain data and collection when you want Collection methods on it. Both store JSON the same way.

Is encryption available for JSON?

Yes. Laravel offers the encrypted:array cast. Note that you can no longer query inside an encrypted value.

References

Article Index & Topic Tags
LaravelDatabaseEloquentJson

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.