Understanding Laravel Eloquent Model Attributes (With Real Examples)
Laravel has quietly introduced a set of PHP attributes that make Eloquent models more explicit, readable, and discoverable. These live under Illuminate\Database\Eloquent\Attributes, but many developers either don’t know they exist or aren’t sure when to use them.
In this article, we’ll walk through what Eloquent model attributes are, what each one does, and how to use them with real examples—beyond what’s shown in the docs.
What are Eloquent Model Attributes?
Eloquent model attributes are PHP 8 Attributes that let you declare model behavior directly on the model class itself.
Instead of relying on naming conventions, service provider registration, or hidden boot logic, these attributes make relationships and behaviour explicit and self-contained.
They don't replace Eloquent fundamentals — but they do improve clarity, especially in larger or long-lived codebases.
#[Boot] — Defining model boot logic explicitly
The #[Boot] attribute marks a static method as a model boot method.
Before:
protected static function booted()
{
static::creating(function ($model) {
// ...
});
}
With #[Boot]:
use Illuminate\Database\Eloquent\Attributes\Boot;
#[Boot]
public static function booting()
{
static::creating(function ($model) {
// ...
});
}
Why this helps
- Boot logic is immediately visible
- Easier to reason about model behavior
- Less “magic” when scanning the class
#[Initialize] — Running logic on model instantiation
The #[Initialize] attribute marks a method that runs when a model is instantiated.
use Illuminate\Database\Eloquent\Attributes\Initialize;
#[Initialize]
public function initializeDefaults()
{
$this->status ??= 'draft';
}
Common use cases
- Setting default attribute values
- Normalizing initial state
- Preparing derived properties
#[ObservedBy] — Attaching observers directly to models
Instead of registering observers in a service provider, you can attach them directly to the model.
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy(PostObserver::class)]
class Post extends Model
{
}
Why this is nice
- Observer relationships are visible at the model level
- No hunting through providers
- Clear ownership of side effects
Laravel docs: https://laravel.com/docs/12.x/eloquent#observers
#[Scope] — Declaring local query scopes
The #[Scope] attribute marks a method as a local query scope.
use Illuminate\Database\Eloquent\Attributes\Scope;
#[Scope]
public function published($query)
{
return $query->whereNotNull('published_at');
}
Usage:
Post::published()->get();
Benefits
- Explicit scope declaration
- Cleaner than relying solely on method naming
Laravel docs: https://laravel.com/docs/12.x/eloquent#local-scopes
#[ScopedBy] — Applying global scopes cleanly
Use #[ScopedBy] to apply a global scope class to a model.
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy(ActiveScope::class)]
class User extends Model
{
}
Ideal for
- Multi-tenant applications
- Business rules that should always apply
- Avoiding duplicated where clauses
Laravel docs: https://laravel.com/docs/12.x/eloquent#global-scopes
#[CollectedBy] — Customizing model collections
This attribute specifies which collection class should be used when retrieving multiple models.
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
#[CollectedBy(CustomPostCollection::class)]
class Post extends Model
{
}
Why it matters
- Cleaner than overriding
newCollection()method. - Keeps collection behavior close to the model
Docs: https://laravel.com/docs/12.x/eloquent-collections#custom-collections
#[UseEloquentBuilder] — Assigning a custom query builder
I actually previously wrote about this. Use this when your model benefits from a richer query API.
use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder;
#[UseEloquentBuilder(PostBuilder::class)]
class Post extends Model
{
}
When this shines
- Domain-specific queries
- Reusable query logic
- Fluent, expressive APIs
This pairs well with advanced querying patterns and avoids bloated models.
#[UseFactory] — Explicit factory mapping
Instead of relying on conventions, you can explicitly define a model’s factory.
use Illuminate\Database\Eloquent\Attributes\UseFactory;
#[UseFactory(PostFactory::class)]
class Post extends Model
{
}
Why use it
- Clear factory ownership
- Less convention-based guessing
Docs: https://laravel.com/docs/12.x/eloquent-factories#factory-and-model-discovery-conventions
#[UsePolicy] — Declaring authorization policies
Attach a policy directly to the model.
use Illuminate\Database\Eloquent\Attributes\UsePolicy;
#[UsePolicy(PostPolicy::class)]
class Post extends Model
{
}
Benefits:
- Authorization rules are discoverable
- No indirection via AuthServiceProvider
Docs: https://laravel.com/docs/12.x/authorization#manually-registering-policies
#[UseResource] — Assigning API resources
For API-driven applications, you can define a default resource.
use Illuminate\Database\Eloquent\Attributes\UseResource;
#[UseResource(PostResource::class)]
class Post extends Model
{
}
Docs: https://laravel.com/docs/12.x/eloquent-resources#concept-overview
#[UseResourceCollection] — Assigning resource collections
Likewise, you can define a default resource collection.
use Illuminate\Database\Eloquent\Attributes\UseResourceCollection;
#[UseResourceCollection(PostCollection::class)]
class Post extends Model
{
}
Docs: https://laravel.com/docs/12.x/eloquent-resources#resource-collections
When should you use these attributes?
These attributes work best when:
- You value explicitness over convention
- The codebase is shared across a team
- Models have non-trivial behavior
- Long-term maintainability matters
You may want to avoid overusing them in:
- Very small projects
- Simple CRUD-only models
- Situations where conventions are already clear
As always, clarity beats cleverness.
Final Thoughts
Eloquent model attributes don’t replace understanding Laravel’s core behavior—but they make that behavior more visible.
When used thoughtfully, they can reduce hidden wiring, improve discoverability and make intent clear to future readers.
They’re another tool in the box—not a requirement—but a powerful one when used intentionally.
By the way
I’m currently building SignDeck, a lightweight tool for collecting client documents and e-Signatures without the usual back-and-forth emails. If you work with clients, you might find it useful.