2026-08-31 · Q&A guide

Set Default JSON Value in Laravel Migration (MySQL 8.0+)

Learn how to assign a default JSON structure to a column in a Laravel migration for MySQL, including handling older MySQL versions and factory integration.

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.

← All posts