Setting Default Database Column Values in Laravel Eloquent Models

When building Laravel applications, you'll often need to set default values for database columns. Whether it's marking new users as inactive, setting default order status, or initializing counters, Laravel provides multiple approaches to handle default values effectively.

Database-Level Defaults vs Model-Level Defaults

Laravel offers two primary methods for setting default values: at the database level through migrations, or at the model level using Eloquent's $attributes property. Understanding when to use each approach is crucial for building maintainable applications.

Database-Level Defaults

Setting defaults in your database migrations ensures data integrity at the lowest level:

// Migration file
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->boolean('is_published')->default(false);
    $table->integer('view_count')->default(0);
    $table->string('status')->default('draft');
    $table->timestamps();
});

This approach has several advantages:

  • Data integrity: Defaults are enforced even when data is inserted outside of Laravel
  • Performance: No additional PHP processing required
  • Database consistency: Works with any application accessing the database

However, changing database defaults requires new migrations, making them less flexible for evolving business requirements.

Model-Level Defaults

Laravel 12's Eloquent models support default attributes through the $attributes property:

class Post extends Model
{
    protected $fillable = [
        'title',
        'content',
        'is_published',
        'status'
    ];

    protected $attributes = [
        'is_published' => false,
        'view_count' => 0,
        'status' => 'draft',
    ];

    protected $casts = [
        'is_published' => 'boolean',
    ];
}

Model-level defaults offer greater flexibility:

  • Easy updates: Change defaults without database migrations
  • Environment-specific: Different defaults for different environments
  • Dynamic values: Can use PHP expressions for complex defaults

Advanced Default Value Techniques

Dynamic Defaults with Accessors

For more complex default logic, you can use accessors to provide computed defaults:

class User extends Model
{
    protected $attributes = [
        'role' => 'user',
        'is_active' => true,
    ];

    protected $appends = ['full_name'];

    // Dynamic default for display name
    public function getDisplayNameAttribute()
    {
        return $this->attributes['display_name'] ?? $this->full_name;
    }

    public function getFullNameAttribute()
    {
        return trim("{$this->first_name} {$this->last_name}");
    }
}

Using Model Events for Complex Defaults

Sometimes you need more sophisticated default logic that depends on other model attributes or external data:

class Order extends Model
{
    protected $attributes = [
        'status' => 'pending',
        'currency' => 'USD',
    ];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($order) {
            // Set order number if not provided
            if (empty($order->order_number)) {
                $order->order_number = 'ORD-' . strtoupper(uniqid());
            }

            // Set default shipping method based on customer location
            if (empty($order->shipping_method)) {
                $order->shipping_method = $order->customer->country === 'US' 
                    ? 'standard' 
                    : 'international';
            }
        });
    }
}

Real-World Examples

E-commerce Product Management

class Product extends Model
{
    protected $attributes = [
        'status' => 'draft',
        'visibility' => 'private',
        'stock' => 0,
        'price' => 0,
        'weight' => 0,
        'tax_class' => 'standard',
        'featured' => false,
    ];

    protected $casts = [
        'featured' => 'boolean',
        'price' => 'decimal:2',
        'weight' => 'decimal:2',
    ];

    public function scopePublished($query)
    {
        return $query->where('status', 'published')
                    ->where('visibility', 'public');
    }
}

User Account Management

class User extends Model
{
    protected $attributes = [
        'role' => 'customer',
        'is_active' => true,
        'email_verified' => false,
        'newsletter_subscribed' => false,
        'timezone' => 'UTC',
        'locale' => 'en',
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'email_verified' => 'boolean',
        'newsletter_subscribed' => 'boolean',
        'email_verified_at' => 'datetime',
    ];

    protected static function boot()
    {
        parent::boot();

        static::creating(function ($user) {
            // Set default timezone based on IP geolocation
            if (empty($user->timezone)) {
                $user->timezone = request()->header('Timezone') ?? 'UTC';
            }
        });
    }
}

Best Practices

1. Combine Both Approaches Strategically

Use database defaults for critical business logic and model defaults for UI/UX conveniences:

// Migration - critical business defaults
Schema::create('subscriptions', function (Blueprint $table) {
    $table->id();
    $table->string('status')->default('trial'); // Business critical
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamps();
});

// Model - UI/display defaults
class Subscription extends Model
{
    protected $attributes = [
        'notifications_enabled' => true, // User preference
        'auto_renew' => true,            // User preference
    ];
}

2. Test Your Defaults

Always test that your default values work as expected:

// tests/Unit/Models/PostTest.php
class PostTest extends TestCase
{
    public function test_post_has_correct_defaults()
    {
        $post = new Post([
            'title' => 'Test Post',
            'content' => 'Test content'
        ]);

        $this->assertFalse($post->is_published);
        $this->assertEquals(0, $post->view_count);
        $this->assertEquals('draft', $post->status);
    }

    public function test_post_defaults_persist_after_save()
    {
        $post = Post::create([
            'title' => 'Test Post',
            'content' => 'Test content'
        ]);

        $post->refresh();
        
        $this->assertFalse($post->is_published);
        $this->assertEquals('draft', $post->status);
    }
}

3. Document Your Defaults

Keep your defaults well-documented, especially when they involve business logic:

class Order extends Model
{
    /**
     * Default attribute values
     * 
     * status: All orders start as 'pending' awaiting payment
     * currency: Default to USD, can be overridden by user location
     * tax_included: Defaults to false for B2B, true for B2C
     */
    protected $attributes = [
        'status' => 'pending',
        'currency' => 'USD',
        'tax_included' => false,
    ];
}

Performance Considerations

When using model-level defaults, be aware of potential performance impacts:

  1. Mass Creation: Model defaults only apply to individual model creation, not bulk inserts
  2. Database Queries: Model defaults require instantiating models, while database defaults don't

For high-volume operations, consider using database defaults or explicit value setting:

// Efficient for bulk operations
Product::insert([
    ['name' => 'Product 1', 'status' => 'draft', 'price' => 0],
    ['name' => 'Product 2', 'status' => 'draft', 'price' => 0],
    // ... hundreds more
]);

// Less efficient - creates model instances
collect($productData)->each(function ($data) {
    Product::create($data); // Applies model defaults
});

Conclusion

Setting appropriate default values is essential for building robust Laravel applications. Database-level defaults provide data integrity and performance, while model-level defaults offer flexibility and maintainability.

The key is choosing the right approach based on your specific requirements:

  • Use database defaults for critical business logic and data integrity
  • Use model defaults for user preferences and UI conveniences
  • Use model events for complex, conditional defaults
  • Always test your defaults thoroughly

By mastering these techniques, you'll build more reliable and maintainable Laravel applications that handle edge cases gracefully and provide better user experiences out of the box.

Setting Default Database Column Values in Laravel Eloquent Models - Ferre Mekelenkamp