Reader Stacks

Storing Arrays and JSON in a Laravel Database Column

The array cast handles the JSON encode/decode transparently — reading the attribute returns a real PHP array directly, with no manual json_decode() call needed anywhere in application code.

Storing Arrays and JSON in a Laravel Database Column

Storing an array or arbitrary structured data in a single database column — tags, settings, preferences — is handled cleanly in Laravel through Eloquent's array or json attribute casting, transparently handling the encode/decode step.

The migration: a JSON column

Schema::table('products', function (Blueprint $table) {
    $table->json('tags')->nullable();
});

Most modern databases (MySQL 5.7+, PostgreSQL) have a native JSON column type — this stores the data as genuine structured JSON rather than a plain text string, and on databases that support it, also allows querying into the JSON structure directly with SQL.

Casting the attribute to an array

class Product extends Model
{
    protected $casts = [
        'tags' => 'array',
    ];
}

Working with it as a plain PHP array

$product->tags = ['sale', 'featured', 'new'];
$product->save();

// Later, reading it back:
echo $product->tags[0]; // "sale" — already a real array, no decoding needed

The array cast handles JSON encoding when saving and decoding when reading, entirely transparently — accessing $product->tags returns a genuine PHP array directly, with no manual json_decode() call needed anywhere in application code that uses the model.

Modifying an array attribute correctly

// WRONG — this doesn't persist, since Eloquent doesn't detect the array was mutated in place
$product->tags[] = 'new-tag';
$product->save();

// CORRECT — reassign the whole array
$tags = $product->tags;
$tags[] = 'new-tag';
$product->tags = $tags;
$product->save();

Eloquent's change detection compares the attribute's value before and after — appending directly to the array in place can fail to register as a genuine change in some Laravel versions/configurations, which is exactly why reassigning the full array (as shown in the correct example) is the safer, more reliable pattern.

Using AsArrayObject for a more ergonomic mutation API

use Illuminate\Database\Eloquent\Casts\AsArrayObject;

protected $casts = [
    'tags' => AsArrayObject::class,
];
$product->tags[] = 'new-tag'; // this DOES work correctly with AsArrayObject
$product->save();

AsArrayObject wraps the value in an object implementing ArrayAccess that properly tracks in-place mutations — this specifically fixes the direct-append problem above, at the cost of the value no longer being a plain array (though it still behaves like one for most array-style access).

Using the object cast, for accessing nested data more fluently

use Illuminate\Database\Eloquent\Casts\AsCollection;

protected $casts = [
    'tags' => AsCollection::class,
];
$product->tags->contains('sale'); // Collection methods work directly

AsCollection casts the stored JSON into a genuine Laravel Collection instance rather than a plain array — worth using specifically when the stored data benefits from Collection's own methods (contains(), map(), filter()) rather than plain array functions.

Querying inside a JSON column

$products = Product::whereJsonContains('tags', 'sale')->get();

whereJsonContains() queries directly into the JSON column's structure at the database level — on a supporting database, this can use a genuine JSON-aware index for reasonable performance, though a heavily and frequently queried JSON field is often still a signal that the data would be better modeled as a proper separate related table instead.