Laravel Best Practices: When a JSON Column Makes More Sense
Imagine you’re building a user profile system, and users can customize two things:
- Email notifications — should they receive emails?
- 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:
- Use property declarations for IDE type hints.
- 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. 🚀