
Laravel updateOrCreate(): Replace Manual first() and if Branching
Replace manual first() plus update/create branches with updateOrCreate(), separate lookup attributes from update values, use wasRecentlyCreated, and understand where atomic upsert() is the better tool.
When code needs to find a record and then either update it or create a new one, it is easy to end up with an extra first(), a manual if, and two almost identical persistence branches.
updateOrCreate() expresses that intent directly: locate a model by one set of attributes, then persist the desired values whether the record already existed or not.
Replace Manual first() and if Branching
$setting = UserSetting::query() ->where('user_id', $userId) ->where('key', 'timezone') ->first(); if ($setting) { $setting->update([ 'value' => $timezone, ]);} else { $setting = UserSetting::create([ 'user_id' => $userId, 'key' => 'timezone', 'value' => $timezone, ]);}$setting = UserSetting::updateOrCreate( [ 'user_id' => $userId, 'key' => 'timezone', ], [ 'value' => $timezone, ],);The code is shorter for a useful reason: the call now exposes two different roles clearly - how the record is identified and what state should be persisted.
The First Array Is Identity, the Second Is New State
$subscription = Subscription::updateOrCreate( [ 'user_id' => $userId, 'provider' => 'github', ], [ 'external_id' => $externalId, 'status' => $status, 'expires_at' => $expiresAt, ],);The first array is used to locate the matching record. If no record exists, those attributes also become part of the newly created model.
The second array contains values to persist. They update an existing model or are merged with the lookup attributes for a new model.
The Method Returns a Persisted Eloquent Model
$profile = Profile::updateOrCreate( ['user_id' => $user->id], [ 'display_name' => $displayName, 'bio' => $bio, ],); $profileId = $profile->id;You do not need a separate save() after updateOrCreate(). The method persists and returns the model, so you can immediately work with its id, relationships, casts, and other Eloquent state.
Detect Whether the Record Was Created
$setting = UserSetting::updateOrCreate( [ 'user_id' => $userId, 'key' => 'locale', ], [ 'value' => $locale, ],); if ($setting->wasRecentlyCreated) { // A new row was inserted.}wasRecentlyCreated is useful when creation needs a separate side effect while the persistence logic itself still stays expressed as one application-level intent.
updateOrCreate() and firstOrCreate() Express Different Intent
// Existing row is returned unchanged.$setting = UserSetting::firstOrCreate( ['user_id' => $userId, 'key' => 'theme'], ['value' => 'dark'],); // Existing row receives the new value.$setting = UserSetting::updateOrCreate( ['user_id' => $userId, 'key' => 'theme'], ['value' => $theme],);firstOrCreate() fits when an existing record should simply be returned unchanged. updateOrCreate() fits when the second array represents the desired current state even for an existing model.
It Also Works Through Eloquent Relationships
$setting = $user->settings()->updateOrCreate( [ 'key' => 'timezone', ], [ 'value' => $timezone, ],);When a model naturally belongs to a parent, calling the method through the relationship is often more expressive. The relationship already provides the parent context, leaving only the child business key in the lookup array.
Important: This Does Not Guarantee Fewer SQL Queries
updateOrCreate() removes manual application branches, but it should not be described as a magic way to always reduce SQL query count.
Laravel still needs to determine whether a matching model exists. For an existing record, an update may follow that lookup. The main benefit is therefore less boilerplate and clearer application intent, not a promise of one SQL statement.
Let the Database Enforce Uniqueness
Schema::table( 'user_settings', function (Blueprint $table) { $table->unique([ 'user_id', 'key', ]); },);If user_id + key logically identifies one record, that rule should exist outside PHP as well. A unique index protects the invariant at the database level, including when multiple requests execute concurrently.
updateOrCreate() makes the application code expressive, while the database constraint remains the final guarantee that one business identity cannot become duplicate rows.
Use upsert() for Bulk or Atomic Upsert Work
UserSetting::upsert( [ [ 'user_id' => $userId, 'key' => 'timezone', 'value' => $timezone, ], [ 'user_id' => $userId, 'key' => 'locale', 'value' => $locale, ], ], [ 'user_id', 'key',upsert() is a better fit when several rows should be processed in one database operation or when atomic insert-or-update semantics at the database level are the priority.
updateOrCreate(), in contrast, returns a concrete Eloquent model and reads naturally in normal application logic that works with one record.
When Not to Use updateOrCreate()
- if an existing row should remain unchanged, use
firstOrCreate(); - if you only need an unsaved model instance, look at
firstOrNew(); - if you are processing many rows, consider
upsert()or a normal bulk update; - if the lookup identity has no clear unique constraint, define the data invariant first;
- if update and create branches contain genuinely different business workflows, one
updateOrCreate()call may hide an important distinction.
The main benefit of updateOrCreate() is not that persistence becomes magically cheaper. It removes accidental complexity and lets the code say directly: this is the record identity, this is its current state, persist it.




