Safeguard
Security Guides•

Laravel fillable or guarded: which should you use?

Use fillable in Laravel: it lists the columns a request may set. An empty guarded array opens every column to mass assignment. Here is how to choose.

Safeguard Security Team
4 min read

Use $fillable in Laravel. It is an allow list, so a column the request can set must be named on purpose. $guarded is a block list, and an empty $guarded = [] makes every column mass assignable, including flags like is_admin that you never meant to expose.

What the two properties do

Mass assignment happens when you hand request data to create, fill or update in one call. The Laravel documentation describes the risk plainly: a user sends an unexpected field, such as is_admin, and it changes a column you did not intend. Eloquent models are protected by default, and you must declare either $fillable or $guarded before create will work.

PropertyMeaningDefault postureRisk when you add a column
$fillableOnly these attributes may be mass assignedClosedNew column stays protected until you list it
$guardedThese attributes may not be mass assignedOpen for everything elseNew column is writable unless you remember to guard it
$guarded = []Nothing is guardedFully openEvery column is writable

Why the allow list ages better

A block list is only as good as your memory. Six months after a model is written, someone adds role, balance or email_verified_at. With $guarded = ['id'] that column is writable from the first deploy. With $fillable it stays locked until a reviewer sees the new line in a diff.

The documentation says that if you unguard a model you should hand-craft the arrays you pass to fill, create and update. That is a workable rule for a small internal tool. It is a poor rule for a codebase with many contributors.

Steps to tighten a model

  1. List the columns a form or API endpoint should set, and nothing else.
  2. Put them in $fillable. Keep ownership, role, status, price and verification columns out.
  3. Build the input array from validated data, not from the whole request.
  4. Turn on strict behaviour in development, shown below.
  5. Search the codebase for guarded = [] and for forceFill, which skips the protection on purpose.
// app/Providers/AppServiceProvider.php
use Illuminate\Database\Eloquent\Model;

public function boot(): void
{
    Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}

By default, attributes missing from $fillable are silently discarded. That is fine in production, but in development it hides mistakes. The call above makes Eloquent throw an exception when code tries to fill a column that is not allowed, so you notice early.

Two details are worth knowing. For JSON columns, each key you want writable must be listed in $fillable, for example options->enabled. And Laravel does not support updating nested JSON attributes when you use $guarded.

Try it yourself

Open the walkthrough below to see how a code scan reports a finding. Click through a result and look at how the flagged line is tied to its source.

Click through it at your own pace, or open it full screen.

Quick check

Is guarded = [] ever acceptable?

It can be reasonable in a seeder or a one-off script where no request data reaches the model. In a controller path, prefer $fillable and validated input.

Does validation replace fillable?

No. Validation checks the values you expect. $fillable limits which columns can be set at all. Use both, and pass only validated data.

Does fillable protect me if I call forceFill?

No. forceFill bypasses the protection, so treat each use as something to review.

Safeguard offers dependency scanning, SBOMs and reachability analysis, with policy gates on pull requests. You can try a free scan at gold.safeguard.sh/scan.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.