Laravel is a framework that prides itself on elegance and clarity—but when it comes to model booting, many developers get confused between boot()
, booted()
, and even trait boots. If you’ve ever stared at an Eloquent model thinking, “Wait, when exactly is my event running?”, you’re in the right place.
This is your one-stop, no-fluff guide to understand, master, and use boot()
, booted()
, and trait
boot methods like a pro.
1. What is Model Booting in Laravel?
Every Eloquent model in Laravel has a lifecycle—from creation, update, deletion, and query-building.
Booting is the static process Laravel runs before the model class is fully ready to be used. It gives you crucial hooks to:
- Add static events (like
creating
,updating
,deleting
) - Apply global scopes
- Initialize default attributes
Laravel’s boot process is flexible, but understanding the order is crucial to avoid “my event didn’t fire” moments.
2. Enter boot()
What it is:
boot()
is a static method on the model itself. Override it when you need to add complex, model-level logic, or anything that requires the older, manual approach.
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected static function boot()
{
parent::boot(); // 🚨 Always call this
static::creating(function ($product) {
$product->uuid = (string) \Str::uuid();
});
}
}
Key Points
- Runs before
booted()
. -
Must call
parent::boot()
to preserve trait and framework booting. - Ideal for global scopes and complex boot logic.
- Runs once per model class (static context).
- Historical Note: In early Laravel versions, this was the only way to register static model events.
3. Meet booted()
What it is:
booted()
is a cleaner, safer alternative introduced in Laravel 7. It's a static method that runs after the model has been fully booted.
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected static function booted()
{
static::creating(function ($product) {
$product->uuid = (string) \Str::uuid();
});
}
}
Why use booted()
?
-
Automatically chains logic—No need to call
parent::booted()
, simplifying development. - Cleaner than overriding
boot()
for simple event registration. - Runs after all trait
boot()
methods and the main modelboot()
. - Safer when multiple developers add boot logic, as it avoids accidental overwrites.
4. Trait Boot Methods
Laravel doesn’t just call boot()
—it also calls trait boot methods automatically.
If a trait defines a method like boot[TraitName]
, Laravel will automatically call it before the model’s boot()
.
trait HasUuid
{
// Note: Method name must be 'boot' + 'TraitName' (camel-cased)
protected static function bootHasUuid()
{
static::creating(fn($model) => $model->uuid = (string) \Str::uuid());
}
}
class Product extends Model
{
use HasUuid;
}
Order of execution matters:
- All trait boots (
bootHasUuid
,bootSoftDeletes
, etc.) - Model
boot()
- Model
booted()
5. Visualization: The Boot Sequence
Here’s how Laravel actually runs things. This sequence is absolute:
[Traits boot* methods] -> [Model::boot()] -> [Model::booted()]
The Takeaway:
- Did you forget
parent::boot()
? The sequence may break. - Did you rely on
booted()
but needed a global scope? Scopes needboot()
because they need to be applied earlier in the chain.
6. When to Use Which
Method | Use Case |
---|---|
boot() |
Global scopes, complex boot logic, anything that needs parent::boot() . |
booted() |
Simple events, cleaner alternative to overriding boot() . The safer default for events.
|
Trait boot |
Shared logic across multiple models (e.g., UUID generation, Soft Deletes). |
7. Real-Life Example: Combining All
trait HasUuid
{
protected static function bootHasUuid() // Trait Boot
{
static::creating(fn($model) => $model->uuid = (string) \Str::uuid());
}
}
class Product extends Model
{
use HasUuid;
protected static function boot() // Model boot()
{
parent::boot(); // REQUIRED
// Logic here runs BEFORE booted()
static::creating(fn($product) => $product->slug = \Str::slug($product->name));
}
protected static function booted() // Model booted()
{
// Logic here runs LAST
static::created(fn($product) => logger("Product {$product->id} created!"));
}
}
Execution order:
-
HasUuid::bootHasUuid()
→ setsuuid
-
Product::boot()
→ setsslug
-
Product::booted()
→ logs creation
✅ All events happen in the right order, and nothing breaks.
8. Common Pitfalls
-
Forgetting
parent::boot()
inboot()
→ Trait boots won’t run, breaking features like Soft Deletes. -
Using
booted()
for global scopes → It might run too late and the scope may not be applied to the initial query. -
Multiple developers overriding
boot()
→ Can overwrite each other's logic;booted()
avoids this problem.
9. TL;DR – The Million-Dollar Takeaway
-
Trait
boots
→ Run first. -
Model
boot()
→ Run second, required to callparent::boot()
. -
Model
booted()
→ Run last, perfect for event registration. - Use the right tool for the job:
boot()
for global logic/scopes,booted()
for cleaner event hooks.
Mastering this will make you the Laravel developer everyone asks for when something “doesn’t fire”.
10. Closing Thoughts
Understanding boot()
, booted()
, and trait boots is not just theory—it’s the difference between fragile code and rock-solid models.
By respecting the boot sequence and using booted()
where possible, you’ll write cleaner, safer, and easier-to-maintain Eloquent models.
Top comments (1)
So you are saying the official documentation got it wrong?
It clearly states use the
ScopedBy
attribute or add them to the booted method.How is this a problem? There is only one
boot
method per model.If that would be a problem it would also be a problem for the
booted
method.