Laravel Best Practices: When a JSON Column Makes More Sense

Dr. Adam Nielsen
2 min readFeb 4, 2025

--

Imagine you’re building a user profile system, and users can customize two things:

  1. Email notifications — should they receive emails?
  2. Dark mode & text size — personal display preferences.

At first, you might add both settings as columns:

Schema::table('users', function (Blueprint $table) {
$table->boolean('email_notifications')->default(true);
$table->boolean('dark_mode')->default(false);
$table->string('text_size')->default('medium');
});

This works, but as more UI preferences are added (e.g., font choice, layout style), the table can become cluttered.

Instead of storing every user setting as a separate column, we can keep essential settings like email_notifications in dedicated columns (since they need to be filterable) and move personal preferences (like dark_mode and text_size) into a JSON column:

Schema::table('users', function (Blueprint $table) {
$table->boolean('email_notifications')->default(true); // Needs to be filterable
$table->json('settings')->nullable(); // Stores UI settings
});

Querying Users with Filterable Columns

If you need to retrieve all users who have email notifications enabled, you can do so efficiently:

$users = User::where('email_notifications', true)->get();

It only remains to understand, how to get/set the json content in settings.

Handling JSON Data in the settings Column

Now, the only remaining question is: How do we get and set values inside the JSON column efficiently? To make accessing user preferences simple and IDE-friendly, you can follow two key steps:

  1. Use property declarations for IDE type hints.
  2. Use Laravel’s Attribute casting for automatic retrieval and storage.

1. Add Property Declarations for IDE Auto-Completion

This helps your IDE recognize dynamic attributes within the model:

/**
* @property array $preferences
* @property bool $dark_mode
* @property string $text_size
*/
class User extends Model

Now, your IDE (at least if you use PHPSTORM) will recognize dark_mode and text_size as available properties on a User instance.

2. Use Laravel’s Attribute Casting for Easy Access

Instead of manually handling JSON encoding/decoding, use Laravel’s Attribute’s to define getters and setters for user preferences:

protected function darkMode(): Attribute
{
return Attribute::make(
get: fn () => $this->settings['dark_mode'] ?? false,
set: fn ($value) => [
'settings' => json_encode(array_merge($this->settings ?? [], ['dark_mode' => $value])),
]
);
}

protected function textSize(): Attribute
{
return Attribute::make(
get: fn () => $this->settings['text_size'] ?? 'medium',
set: fn ($value) => [
'settings' => json_encode(array_merge($this->settings ?? [], ['text_size' => $value])),
]
);
}

Now You Can Use These Attributes Like Regular Columns

$user = User::find(1);

// Retrieve values
echo $user->dark_mode; // false (default)
echo $user->text_size; // "medium"

// Update values
$user->dark_mode = true;
$user->text_size = 'large';
$user->save();

Why This Approach?

Cleaner code — No need to manually handle JSON encoding/decoding.
Better IDE support — Autocomplete works for dynamically defined properties.
Laravel magic — Works like a normal column, but with the flexibility of JSON storage. 🚀

--

--

Dr. Adam Nielsen
Dr. Adam Nielsen

Written by Dr. Adam Nielsen

PHD in math. and Laravel / Vue Full-Stack-Developer

No responses yet