Mastering Laravel Eloquent model state with exists and wasRecentlyCreated helps you handle database records reliably. Learn to distinguish new from old.
Last month, I spent about two days debugging a silent bug in a user synchronization script. The system was sending redundant "Welcome" emails every time a user profile was updated, simply because I assumed that if a model instance existed, it must have been saved to the database. I was wrong.
In Laravel, knowing exactly what's happening with your database records is a fundamental skill. If you're building features that need to distinguish between a fresh insert and an existing record update, you need to master laravel eloquent state tracking.
When you work with Eloquent, it’s easy to treat models like simple PHP objects. But they aren't. They are active records tied to a specific state in your database. Two of the most useful, yet often misunderstood, properties are $model->exists and $model->wasRecentlyCreated.
exists mattersThe exists property is a boolean that tells you if the model currently resides in the database.
When you instantiate a new model like $user = new User;, exists is false. Once you call $user->save(), it flips to true. This seems simple, but it’s critical when you’re writing logic that needs to handle both creation and updates.
wasRecentlyCreatedThis is where developers often get tripped up. wasRecentlyCreated is only true immediately after a model has been persisted via a method like firstOrCreate or updateOrCreate.
I once tried to use wasRecentlyCreated on a model that had been fetched from the database using User::find(1). It returned false every single time, which led to a logic branch that never executed. It’s a common mistake; remember that this property is only populated during the initial save of that request cycle.
Let’s look at a common scenario: you’re importing a CSV of products and need to decide whether to send an "In Stock" notification.
PHP$product = Product::updateOrCreate( ['sku' => $data['sku']], ['price' => $data['price'], 'stock' => $data['stock']] ); if ($product->wasRecentlyCreated) { #6A9955">// This is a brand new product in our system Notification::send(new NewProductAdded($product)); } else { #6A9955">// We just updated an existing record Log::info("Updated stock for SKU: {$product->sku}"); }
This approach is much cleaner than manually checking the database for the record's presence before saving. It leverages the underlying eloquent lifecycle to do the heavy lifting for you.
Before I started using these properties effectively, I used to write verbose checks. I would fetch the record, check if it was null, then perform an insert or update. It looked something like this:
PHP#6A9955">// The old, inefficient way $user = User::where('email', $email)->first(); if (!$user) { $user = User::create(['email' => $email]); #6A9955">// Do extra logic here... } else { $user->update(['last_login' => now()]); }
This is essentially what updateOrCreate does internally, but it’s much more readable and less prone to race conditions. If you're dealing with complex logic, you might want to look into Mastering Laravel Observers: A Beginner’s Guide to Automation to handle these side effects cleanly without cluttering your controller.
exists persist across requests?No. exists and wasRecentlyCreated are properties of the specific PHP object instance in memory during the current request. Once the request finishes and the object is destroyed, these states are gone.
exists to true?You can, but you shouldn't. Setting $model->exists = true tells Eloquent to treat the object as if it's already in the database. If you do this with a model that doesn't actually exist in your table, your next save() call will trigger an UPDATE query instead of an INSERT, which will fail or do nothing.
Checking these properties is near-instant because they are just boolean flags on the object. The real php development bottleneck usually happens if you perform unnecessary database queries to check for existence before saving. Stick to methods like updateOrCreate to keep your database interactions efficient.
Managing model state effectively is a mark of a developer who has moved past the "get it working" phase into the "get it working reliably" phase. I still catch myself over-engineering these checks sometimes. When in doubt, lean on the built-in Eloquent methods.
Next time you’re writing a sync job or a form handler, pause and ask: "Do I actually need to query the database again, or can I trust the model instance I already have?" You'll likely find that you have all the information you need right there. If you're looking to optimize further, you might also find that Mastering Laravel Cache: A Beginner's Guide to Performance helps when you're dealing with high-frequency model lookups that aren't strictly tied to the immediate lifecycle.
Mastering Laravel local scopes helps you write cleaner, more readable database queries. Learn how to encapsulate filtering logic directly within your models.