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 practices.
On this page
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
$fillableover$guardedby 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.
Newsletter
A weekly newsletter on React, Next.js, AI-assisted development, and engineering. No spam, unsubscribe any time.