Reader Stacks

Storing Arrays and JSON in Laravel Eloquent (Casts)

A cast is what turns a raw JSON text column into a working PHP array automatically on every read and write — without one, Eloquent hands back and expects a plain JSON string instead.

A json column stores its data as raw JSON text at the database level — without telling Eloquent how to handle that column, reading it back gives a plain PHP string containing JSON, not a usable array, and assigning a raw PHP array to it fails rather than being auto-encoded.

1. Setting up an array cast

Schema::table('products', function (Blueprint $table) {
    $table->json('tags')->nullable();
});
class Product extends Model
{
    protected function casts(): array
    {
        return [
            'tags' => 'array',
        ];
    }
}

2. What the cast actually changes

$product->tags = ['waterproof', 'lightweight', 'bestseller'];
$product->save(); // automatically JSON-encoded before being written to the database

$product->refresh();
$product->tags; // automatically JSON-decoded back into a PHP array on read

Without the array cast, that same assignment would need a manual json_encode() before saving and a manual json_decode() after reading — the cast makes the column behave like a native PHP array throughout the model's code, handling the encoding and decoding transparently in both directions.

3. array vs. json casts

'tags' => 'array',   // decodes to a plain PHP array
'settings' => 'object', // decodes to a stdClass object instead of an array
'metadata' => AsCollection::class, // decodes to a Laravel Collection, with all of Collection's methods

array is the right default for most cases. AsCollection::class is worth reaching for specifically when the stored value benefits from Collection methods (->filter(), ->map(), ->contains()) being called on it directly, rather than needing to wrap a plain array in collect() manually every time it's used.

4. Casting to a value object with AsFluent, or a custom cast class

protected function casts(): array
{
    return [
        'address' => AsFluent::class, // dot-notation access: $product->address->get('city')
    ];
}

For a JSON structure with a fixed, known shape rather than an arbitrary array, a custom cast class (implementing CastsAttributes) can decode it into a proper typed object with its own methods, rather than a loosely-typed array — more setup, but stronger guarantees about the shape of the data once it's cast.

5. Default values for a JSON column

protected $attributes = [
    'tags' => '[]', // stored as the literal JSON string "[]", the empty-array default
];

Setting a model-level default (rather than only a database-level default) ensures a newly instantiated, unsaved model already has a sensible, non-null value for the cast attribute — useful when code elsewhere assumes $product->tags is always at least an empty array, never null.

6. Encrypted JSON, for sensitive structured data

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

encrypted:array combines both behaviors — the value is JSON-encoded and then encrypted before being written to the database, and decrypted and decoded automatically on read. Worth using specifically for structured data that shouldn't be readable in plain text directly from the database (an admin's raw SQL access, a database backup) even though the application itself needs to work with it as a normal array.

Topics: Database Queries & Eloquent