MySQL JSON Default Constraints
MySQL 8.0.13 introduced support for default values on JSON columns. The value must be a JSON literal, not an expression. Earlier MySQL releases do not allow defaults on JSON fields at all.
Laravel Migration Example
Use a raw JSON string in the migration. Laravel’s Schema builder will escape the value correctly. The JSON must be a valid literal, so escape inner quotes with backslashes or use single quotes for the PHP string.
```php Schema::create('users', function (Blueprint $table) { $table->id(); $table->json('settings') ->default('{"mail":{"hasNewsletter":false},"time":{"timezone":""}}'); $table->timestamps(); }); ```
```php
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->json('settings')
->default('{"mail":{"hasNewsletter":false},"time":{"timezone":""}}');
$table->timestamps();
});
```
Handling Older MySQL Versions
If you’re on MySQL < 8.0.13, the default clause will fail. Two common workarounds are:
1. **Trigger** – Set the column after insert. 2. **Model Mutator** – Provide a default in the Eloquent model.
Trigger example:
```sql CREATE TRIGGER set_default_settings AFTER INSERT ON users FOR EACH ROW SET NEW.settings = COALESCE(NEW.settings, '{"mail":{"hasNewsletter":false},"time":{"timezone":""}}'); ```
```sql
CREATE TRIGGER set_default_settings AFTER INSERT ON users
FOR EACH ROW
SET NEW.settings = COALESCE(NEW.settings, '{"mail":{"hasNewsletter":false},"time":{"timezone":""}}');
```
Factory & Observer Integration
When using factories, define the default value directly in the factory definition so the observer doesn’t overwrite it.
```php $factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'settings' => json_encode([ 'mail' => ['hasNewsletter' => false], 'time' => ['timezone' => ''], ]), ]; }); ```
If you still need an observer, guard against overwriting:
```php public function created(User $user) { if (empty($user->settings)) { $user->settings = json_encode([ 'mail' => ['hasNewsletter' => false], 'time' => ['timezone' => ''], ]); $user->save(); } } ```
Best Practice Summary
Use the native MySQL default for JSON when available. For older MySQL, rely on a trigger or model mutator. Ensure factories provide the same default to keep test data consistent.
Takeaway: Define a JSON literal default in the migration for MySQL 8.0+, or use a trigger/mutator for older versions, and align factory definitions to avoid observer conflicts.
People also ask
Can I use a PHP array in the default clause?
No. The default must be a JSON literal string; convert the array with json_encode before passing it to default().
Will the default be applied to existing rows?
No. It only affects new inserts; update existing rows manually or via a migration script.
Inspired by a public discussion on Stack Overflow. This article is an original explanation for learners.