Laravel Models — $fillable or $guarded?

Paul Michael
4 min readMay 18, 2021

--

Laravel is a great PHP framework that I have been using almost exclusively for more than six years now, so it’s no surprise that I have come to find my own structure and coding style when using it. Of course, that doesn’t detract the opinions of others — as with many things in development, there are many ways to write code or use a framework. Today I’d like to talk about the $fillable and $guarded properties available to us in Eloquent models.

In case you’re not aware there are two ways in which you could create or update a record using Active Record in Laravel via Eloquent:

Create a model instance, assign values and save

$someModel = new SomeModel();
$someModel->title = 'Some Title';
$someModel->content = 'Some content goes here.';
$someModel->is_active = true;
$someModel->save();

Mass assign

$someModel = SomeModel::create([
'title' => 'Some Title',
'content' => 'Some content goes here.',
'is_active' => true,
]);

Both methods achieve the same result — they create a new record in the database and return the resulting Eloquent object. Creating a model instance first doesn’t require anything else to be configured in the model, and will just work.

If you attempt to run the second example, Laravel will return a MassAssignmentException. This is where $fillable or $guarded comes into play — consider the two examples below.

Fillable

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class SomeModel extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content',
'is_active',
];
}

Guarded

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class SomeModel extends Model
{
use HasFactory;
protected $guarded = [
'id',
];
}

In both cases the mass assignment code example will now work, but which should you choose, and why does it matter?

Firstly, $fillable is your allow list. Any column you add to this array will be affected in a mass assignment query, which conversely means $guarded is your deny list, where any column listed will not be affected.

Many developers therefore see using $guarded as the better choice — you normally set it once and pretty much forget about it. Below are some of the reasons I’ve come across for using $guarded :

  • “It’s convenient”
    The biggest reason I’ve seen for using $guarded is that you set it and forget it. But this can lead to unforeseen security vulnerabilities if you forget to add a new column that should be protected.
  • “It’s cleaner than dozens of column names in a $fillable array”
    While this can often be true, we should never base the method of implementation on how clean it looks.
    See also: “All those columns. Ugh.”
  • “I don’t have to keep remembering to add a column name to a model,” or, “it stops me spending ages trying to work out why my query doesn’t work properly.”
    This goes hand in hand with “it’s convenient”, but suggests laziness. And yes, developers by nature can be lazy — that’s why we have a plethora of packages and frameworks. Adding a database column in the first place is a conscious decision you make when you write that migration or manually modify your database tables — adding the column name to your model should be part of that conscious decision and effort.
  • “I don’t use either, but use FormRequest validators instead, and don’t use mass assign”
    While this works, you’re entrusting mass assign database access to a form validator. What’s more, there may be one or more validators that affect the model in question, meaning there is scope for some big issues if something is missed. In addition to this, if another developer comes onto the project and hasn’t kept the validator pattern and chooses to use mass assign… well, you can see where I’m going.

Security should never be at the expense of convenience.

Most of the justification for using $guarded over $fillable I see and hear are largely for convenience, taking a trusting approach over your data layer because it’s easier.

However, history shows us that systems are typically deny first — that is, block everything until we say otherwise. Windows NT does this with file permissions, firewalls require explicit allow rules, UNIX file permissions are restrictive first. It’s certainly not convenient to have to add allow rules to firewalls and file permissions, but it’ll save your skin.

So why treat your application’s data layer any differently?

I use $fillable — whether I have 3 columns to add or 50. I’m of the opinion that your model should “know” about the columns you deem acceptable for general manipulation. I also know that if another developer comes onto the project, that developer will be able to look at the model class and see exactly which columns can be modified.

It’s more secure, because if I accidentally forget to add the column name in the model the worst case scenario is that my update won’t work. It’s a failsafe that prevents me from doing something potentially stupid.

And it’s more secure because, whether I’m using a request validator or not, the last line of defence in the framework before the data is committed is the model. We should strive to add more layers of security to a potentially attack vector, not strip all but one away.

For me I sleep better at night using $fillable

--

--

Paul Michael
Paul Michael

Written by Paul Michael

Founder, technical director and software developer | Instrument Rated (IR(R)) Pilot 🛩 | Photographer 📸 Helping businesses be more efficient and profitable.