Laravel

Mass Assignment Guarding in Laravel

How to protect your Laravel models from mass assignment vulnerabilities using the fillable and guarded properties, with practical examples and best prac...

Mass assignment is a feature that lets you populate a model's attributes from an array in one step. It is convenient, but it becomes a security vulnerability if you pass user-controlled input directly without specifying which fields are allowed to be filled.

The Vulnerability

Imagine a users table with an is_admin column. Without guarding, this request would silently promote the user to admin:

POST /users
{
    "name": "Alice",
    "email": "[email protected]",
    "is_admin": true
}
// Dangerous — never do this
User::create($request->all());

Using $fillable

The $fillable property is an allowlist. Only the fields listed here can be mass-assigned:

class User extends Model
{
    protected $fillable = [
        'name',
        'email',
        'password',
    ];
}

Any field not in $fillable is silently ignored when using create() or fill(). The is_admin field above would be discarded.

Using $guarded

The $guarded property is a denylist — it specifies which fields cannot be mass-assigned. Everything else is allowed:

class User extends Model
{
    protected $guarded = [
        'is_admin',
        'role',
    ];
}

An empty $guarded array ($guarded = []) disables all guarding. Avoid this in production.

$fillable vs $guarded

Prefer $fillable. It forces you to consciously opt in to each field, which is the safer default. $guarded requires you to remember every sensitive field that must be excluded — easy to miss when adding new columns.

Using $request->validated() in Controllers

The model's $fillable property is a last line of defence. The better practice is to validate input first and only pass validated data to the model:

public function store(StoreUserRequest $request): RedirectResponse
{
    // validated() returns only the fields that passed your rules
    User::create($request->validated());

    return redirect()->route('users.index');
}

A Form Request defines exactly which fields are expected. Combined with $fillable, this gives you two independent layers of protection.

Checking for Vulnerabilities

If you set $guarded = [] and call create($request->all()), PHPStan with Larastan will not warn you — it is valid PHP. Instead, rely on code review and the principle of always using $request->validated() over $request->all().

Tips

  • Use $fillable over $guarded by default.
  • Always call $request->validated() in controllers, not $request->all() or $request->input().
  • Review your models when adding new sensitive columns — if the column should never be user-writable, check that it is not in $fillable.
  • In tests, use factories to create test data rather than calling create() with raw arrays from user input.
← Older
Monitoring and Alerting in Laravel
Newer →
Logging in Laravel

Newsletter

A weekly newsletter on React, Next.js, AI-assisted development, and engineering. No spam, unsubscribe any time.