Understanding protected, $fillable, and #[Fillable] in Laravel

If you’re learning Laravel, you may have noticed something unusual in newer Laravel models:

#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
//
}

If you’ve learned the traditional Laravel approach, you might be wondering:

What happened to protected $fillable and protected $hidden?

And more importantly:

Is #[Fillable] a property? Can I make it protected?

To understand this properly, we need to separate three PHP concepts:

  1. Properties
  2. Visibility
  3. Attributes

Let’s start from the beginning.

1. What is a Property in PHP?

A property is essentially a variable that belongs to a class or object.

For example:

class User
{
public $name;
public $email;
}

Here, $name and $email are properties of the User class.

We can create an object:

$user = new User();
$user->name = "Aditya";
$user->email = "aditya@example.com";

Now the object contains data:

User object

├── name → "Aditya"
└── email → "aditya@example.com"

So when you see:

$user->name

name is a property belonging to the $user object.

2. Properties Can Have Visibility

PHP allows us to control who can access a property.

There are three main visibility modifiers:

public
protected
private

For example:

class User
{
public $name;
protected $email;
private $password;
}

These three properties have different access rules.

public

A public property can be accessed from anywhere.

$user = new User();
$user->name = "Aditya";
echo $user->name;

This works because $name is public.

protected

A protected property can be accessed inside the class and by child classes.

class User
{
protected $email;
}

You cannot normally access it directly from outside:

$user = new User();
$user->email = "aditya@example.com";

This will result in an access error because $email is protected.

private

A private property can only be accessed from within the class that declared it.

class User
{
private $password;
}

Outside the class:

$user->password = "secret";

is not allowed.

3. What Does protected $fillable Mean?

Now let’s look at Laravel.

A traditional Laravel User model might contain:

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

Let’s break this apart.

protected

is the visibility modifier.

$fillable

is the property name.

[
'name',
'email',
'password'
]

is the value stored in that property.

So:

protected $fillable = [
'name',
'email',
'password',
];

means:

Create a protected property called $fillable containing these three values.

4. Why Does Laravel Need $fillable?

Laravel uses $fillable to control mass assignment.

For example:

$user = User::create([
'name' => 'Aditya',
'email' => 'aditya@example.com',
'password' => 'secret',
]);

Laravel needs to know which fields are allowed to be assigned this way.

That’s where $fillable comes in:

protected $fillable = [
'name',
'email',
'password',
];

It tells Laravel:

These attributes are allowed for mass assignment.

This is an important security mechanism because you don’t want users to arbitrarily assign sensitive database fields.

5. So What Is #[Fillable(...)]?

Now we come to the newer syntax:

#[Fillable(['name', 'email', 'password'])]

This looks similar to $fillable, but it is fundamentally different.

It is not a property.

It is a PHP Attribute.

PHP introduced Attributes in PHP 8.

The #[] syntax identifies an Attribute:

#[SomeAttribute]

You can think of an Attribute as metadata or additional instructions attached to PHP code.

For example:

#[Fillable(['name', 'email', 'password'])]
class User extends Authenticatable
{
}

The Fillable attribute is attached to the User class.

Laravel can then read that information and use it to configure the model.

6. Why Can’t We Write protected #[Fillable(...)]?

This is where the difference becomes important.

You might think:

protected #[Fillable(...)]

But this is not how PHP Attributes work.

Why?

Because:

protected $fillable;

is a property declaration.

While:

#[Fillable(...)]

is an attribute.

They are different language constructs.

Think of it like this:

protected $fillable
│ │
│ └── Property

└── Visibility

Whereas:

#[Fillable(...)]

└── Attribute / Metadata

There is no protected attached to the Attribute itself.

7. You Can Put Attributes on Properties

Here’s where it gets interesting.

PHP Attributes can actually be attached to different things, including properties.

For example:

class User
{
#[SomeAttribute]
public string $name;
}

Here:

public string $name;

is still the property.

And:

#[SomeAttribute]

is metadata attached to that property.

So the concepts can exist together:

class User
{
#[SomeAttribute]
protected string $name;
}

Here:

  • protected → visibility
  • $name → property
  • string → property type
  • #[SomeAttribute] → attribute

8. A Simple Mental Model

Imagine a car.

The properties describe what the car has:

class Car
{
public string $color;
protected int $speed;
}

The car has:

color
speed

Those are properties.

The visibility tells us who can access them:

public    → everyone
protected → class + children
private → class only

Now imagine putting a label on the car:

#[Electric]
class Car
{
}

#[Electric] isn't a property such as $color.

It’s metadata telling PHP or a framework something about the class.

9. Traditional Laravel vs Attribute-Based Laravel

Traditional Laravel:

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

Here we have two properties:

$fillable
$hidden

Both are protected.

With the attribute approach:

#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
}

Now we don’t have $fillable and $hidden properties written in the class.

Instead, we’re attaching Laravel attributes to the class.

10. Don’t Confuse $fillable With Fillable

This tiny difference is extremely important.

$fillable

protected $fillable = [
'name',
'email',
];

$fillable is a property.

Fillable

#[Fillable([
'name',
'email',
])]

Fillable is an Attribute class.

Notice the difference:

$fillable

Property

versus:

Fillable

Attribute

The $ is a useful clue that you're dealing with a variable/property.

11. What About Methods?

The same concept applies to methods.

A method is an action:

class User
{
public function login()
{
// ...
}
}

You can also attach an Attribute to a method:

class User
{
#[SomeAttribute]
public function login()
{
// ...
}
}

Here:

public

controls visibility.

login()

is the method.

#[SomeAttribute]

is metadata attached to the method.

Again, these are separate concepts.

12. The Three Things You Should Remember

When reading PHP/Laravel code, mentally separate these:

Property

protected $fillable;

A property stores data belonging to an object.

Visibility

public
protected
private

Visibility controls who can access a property or method.

Attribute

#[Fillable(...)]

An Attribute provides metadata/instructions that PHP or a framework can read.

13. Final Example

Let’s put everything together:

#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable
{
protected string $email;
private string $password;
public function login()
{
// Login logic
}
}

Now identify each part:

#[Fillable(...)]

Attribute

#[Hidden(...)]

Attribute

protected string $email
│ │ │
│ │ └── Property
│ └────────── Type
└──────────────────── Visibility

private string $password
│ │ │
│ │ └── Property
│ └────────── Type
└────────────────── Visibility

public function login()
│ │
│ └── Method
└───────── Visibility

Once you understand this distinction, Laravel’s newer syntax becomes much less confusing.

The Golden Rule

Whenever you see:

public
protected
private

think:

Who is allowed to access this?

Whenever you see:

$something

inside a class, think:

This is probably a property.

Whenever you see:

#[Something(...)]

think:

This is an Attribute — metadata/instructions attached to PHP code.

These three concepts are related, but they are not interchangeable.

Understanding this distinction is one of those small PHP concepts that makes reading modern Laravel code dramatically easier.