Laravel Database

Use Database Expressions for Dynamic Migration Defaults

Pass an Expression instance to Laravel's migration default() method to define raw SQL functions, dynamic timestamps, and calculated defaults directly in your database schema.

In Laravel migrations, the default() method is commonly used to assign static fallback values to newly inserted database columns:

$table->string('status')->default('pending');
$table->unsignedInteger('retry_count')->default(0);
$table->boolean('is_active')->default(true);

When passing a scalar value (such as a string, integer, or boolean), Laravel's schema grammar quotes and escapes the value as a literal in the generated DEFAULT '...' SQL clause.

However, database engines also support native SQL functions, dynamic timestamps, and calculated expressions for column defaults. To pass a raw SQL expression instead of a literal string, wrap the value in an Illuminate\Database\Query\Expression object.

Literal Values vs Database Expressions

Passing a literal string quotes the value, which causes syntax errors or unexpected behavior when attempting to invoke SQL functions:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Query\Expression;

Schema::create('orders', function (Blueprint $table) {
    $table->id();

    // Standard static defaults (strings, integers, booleans)
    $table->string('status')->default('pending');
    $table->unsignedInteger('retry_count')->default(0);

    // ❌ Literal string: Generates DEFAULT 'CURRENT_TIMESTAMP'
    $table->timestamp('placed_at')->default('CURRENT_TIMESTAMP');

    // ✅ Expression object: Generates DEFAULT CURRENT_TIMESTAMP
    $table->timestamp('placed_at')->default(new Expression('CURRENT_TIMESTAMP'));

    // ✅ Database-generated JSON default in MySQL: Generates DEFAULT (JSON_OBJECT())
    $table->json('settings')->default(new Expression('(JSON_OBJECT())'));

    // ✅ Database-generated UUID default: Generates DEFAULT (UUID())
    $table->uuid('order_uuid')->default(new Expression('(UUID())'));
});

The difference is structural:

  • Literal default: Laravel escapes the value as a static string literal.
  • Database expression: Laravel emits the expression unquoted, instructing the database engine to evaluate it upon insert.

Useful Examples

1. Database-Generated Timestamps

Allow the database server to assign timestamps directly upon record creation:

// Standard SQL timestamp default
$table->timestamp('published_at')->default(new Expression('CURRENT_TIMESTAMP'));

// PostgreSQL current date default
$table->date('active_since')->default(new Expression('CURRENT_DATE'));

2. Database-Generated JSON Defaults

Database systems require valid JSON functions or typed literals for column defaults:

// MySQL 8.0+ JSON constructor functions
$table->json('settings')->default(new Expression('(JSON_OBJECT())'));
$table->json('tags')->default(new Expression('(JSON_ARRAY())'));

// PostgreSQL JSONB typed literal
$table->jsonb('preferences')->default(new Expression("'{}'::jsonb"));

3. Native UUID Generation

When the database should generate unique identifiers automatically without requiring PHP-side generation:

// PostgreSQL UUID generation
$table->uuid('id')->primary()->default(new Expression('gen_random_uuid()'));

// MySQL 8.0+ UUID generation (enclosed in parentheses)
$table->uuid('tracking_code')->default(new Expression('(UUID())'));

4. Calculated and Conditional Expressions

Some database systems support conditional logic or computed expressions for default column values:

// MySQL 8.0+ conditional default expression
$table->integer('discount_rate')->default(new Expression(
    '(CASE WHEN quantity >= 10 THEN 10 ELSE 0 END)'
));

(Note: Complex calculated expressions are database-specific and require engine-level support).

Why Use the Database for Defaults?

Defining dynamic defaults at the schema level provides several advantages:

  • Consistency outside Eloquent: Records created via raw SQL queries, bulk insert statements, database seeders, or third-party tools automatically receive valid default values.
  • Centralized authority: Timestamps and UUIDs are generated directly by the database server, avoiding clock drift between application servers.
  • Reduced application logic: Eliminates the need for model event hooks (creating) just to populate basic initial state.

Database Compatibility Considerations

Database expressions are inherently driver-dependent and version-specific:

  • MySQL 8.0+: Functional default expressions must be enclosed in parentheses (for example, (JSON_ARRAY()), (JSON_OBJECT()), or (UUID())).
  • PostgreSQL: Accepts native SQL functions directly (such as gen_random_uuid() or CURRENT_TIMESTAMP) and typed string literals (such as '{}'::jsonb).
  • SQLite & SQL Server: Syntax and function availability vary significantly between versions.

Laravel passes the raw SQL string directly to the active database connection without translating syntax between engines. Always verify that your default expression is supported by your target database system and version.

When NOT to Use Expression

Do not wrap standard static values in an Expression:

// ❌ Unnecessary: loses type safety and cross-database portability
$table->string('status')->default(new Expression("'pending'"));

// ✅ Preferred: clear, portable, and handled automatically by Laravel
$table->string('status')->default('pending');

Use normal default() values for static scalars, and reserve Expression strictly for when you need database-level evaluation.

Summary

  • Use standard default('value') for static strings, numbers, and booleans.
  • Use default(new Expression('...')) when the database engine must evaluate an SQL function, timestamp, or dynamic expression upon insertion.
  • Verify expression syntax against your target database engine, keeping in mind engine-specific rules like MySQL's requirement for enclosing parentheses.
Tags: Laravel Migrations Database Eloquent SQL
Share on X

// Found an issue or want to contribute a tip? github.com/MrPunyapal/tips