Skip to content

Laravel Default Attribute Values: $attributes Explained

How Laravel’s $attributes property sets Eloquent model defaults, why the database default isn’t enough, the raw-format rule, and where defaults don’t apply.

Dasun Kalupahana 3 min read 66 views 0 comments
On this page
  1. The basic example
  2. Why the database default is not enough
  3. The one rule: use the raw, storable format
  4. What defaults do not do
  5. When a default needs to be calculated
  6. Model default or database default?
  7. References

Create a new Eloquent model, ask for $flight->delayed, and you get null. Not false, even if your database column defaults to false. That gap between "the database knows the default" and "the model knows the default" causes small, annoying bugs. Laravel gives you a one-property fix: $attributes. This article shows how it works, where it stops helping, and the format mistake that throws an error. Everything here was run against Laravel 13.35.0 Eloquent and MariaDB.

The basic example

Add an $attributes array to your model:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    /**
     * The model's default values for attributes.
     *
     * @var array<string, mixed>
     */
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
    ];
}

This is the example from the Laravel documentation. A new instance now carries those values from the moment it is created, before anything touches the database:

$flight = new Flight;

$flight->delayed;   // false
$flight->options;   // '[]' (or [] if you cast it, see below)

Why the database default is not enough

Say your migration has $table->boolean('delayed')->default(false) and $table->string('status')->default('scheduled'), but the model has no $attributes. I ran this:

$flight = Flight::create(['name' => 'A']);

$flight->delayed;   // null  (the row in the database has 0)
$flight->status;    // null  (the row in the database has 'scheduled')

$flight->refresh();
$flight->status;    // 'scheduled'

The insert never mentioned those columns, so the database filled them in, and the model object never found out. If you return that model straight from an API controller, your JSON says null while the database says something else. Putting the same defaults in $attributes removes the mismatch, and it also makes the value available on a fresh new Flight for forms and validation.

The one rule: use the raw, storable format

The docs say values in $attributes must be in the raw format, as if just read from the database. That is why the example uses the string '[]', not an empty array. If you cast the column, Laravel converts the raw value when you read it:

class Flight extends Model
{
    protected $attributes = [
        'options' => '[]',
        'delayed' => false,
        'status'  => 'scheduled',
    ];

    protected function casts(): array
    {
        return [
            'options' => 'array',
            'delayed' => 'boolean',
            'status'  => FlightStatus::class,   // a backed enum
        ];
    }
}

In my test, a new instance returned [] for options, false for delayed, and the enum case for status, and a plain save() wrote '[]', 0 and 'scheduled' to the row. The mistake to avoid is putting [] (a real array) in $attributes next to an array cast. I tried it, and just reading the attribute threw a TypeError from json_decode() because it received an array instead of a string. Use '[]'.

What defaults do not do

These are the behaviors that surprise people, all confirmed by running them:

SituationWhat happens
Flight::create(['delayed' => true])Your value wins over the default
Flight::create(['status' => null]) on a NOT NULL columnThe default is replaced by null and the database rejects it (integrity constraint error, column cannot be null)
Flight::create(['options' => null]) on a nullable columnnull is stored
Flight::select('id')->first()Defaults are not added. Only id is present and $flight->delayed is null
Loading a row from the databaseDatabase values are used, the defaults do not override them

The second row is the important one. A model default only applies when the attribute is not set. If a request passes an explicit null, it wins. Keep your validation in place and do not treat $attributes as a safety net.

When a default needs to be calculated

The $attributes property holds fixed values only. A token, a reference number or anything based on the current time needs code. A creating event works (I tested the same pattern with a generated code and it was saved on insert):

use Illuminate\Support\Str;

protected static function booted(): void
{
    static::creating(function (Flight $flight) {
        $flight->code ??= 'FL-' . strtoupper(Str::random(6));
    });
}

For UUID or ULID primary keys, Laravel has dedicated traits (HasUuids and HasUlids), so you do not need this pattern for IDs.

Model default or database default?

You can use both, and for important columns I would. They protect different things:

  • Database default: protects the data from every writer, including raw queries, imports, other apps and bulk insert() calls that skip models.
  • $attributes: makes the model object correct right away, with no refresh and no extra query.

Just keep the two values identical. If they drift apart, the model and the database will disagree about what "default" means, and that is a very hard bug to spot.

References

Free resources for this topic

Code snippets and fixes for this topic

Get one practical tip every week

New tutorials, fixes and free cheat sheets from DEV Talk, straight to your inbox. No spam, unsubscribe in one click.

Free download: Laravel interview questions (PDF)

Have a question about this post?

Ask the community: other developers and the author can answer.

Ask the community

Get notified about new articles

One click, no email needed. Turn it off anytime with the bell at the top.

Get new articles on your iPhone

Add DEV Talk to your Home Screen, open it, then tap the bell.

Rate this post
0.0
Share

Enjoying the content?

If this article helped you, you can support the author with a coffee. It keeps new posts coming.

Buy me a coffee (opens in a new tab)

Comments 0

Be kind. Comments with links are reviewed before they appear.

No comments yet

Be the first to share your thoughts.

Get new articles in your inbox

Practical tutorials and deep dives, about once a week. No spam, unsubscribe anytime.

Privacy policy

Add DEV Talk to your Home Screen

  1. 1 Tap the Share button in Safari's toolbar.
  2. 2 Scroll down and tap Add to Home Screen.
  3. 3 Tap Add. Open it from the Home Screen to read like an app and to turn on notifications with the bell.

We use cookies to improve your experience and analyze traffic. By continuing, you agree to our Privacy Policy.