
Laravel withWhereHas(): Avoid Duplicating Conditions in with() and whereHas()
Replace the same condition repeated in whereHas() and with() with withWhereHas(), filtering parent models and eager loading only matching related records.
Sometimes a query needs to do two things at once: return only models that have related records matching a condition, and eager load only those matching related records.
Using separate whereHas() and with() calls can duplicate the same condition. Laravel provides withWhereHas() for this case.
Replace the Same Condition in whereHas() and with()
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->with([ 'comments' => fn ($query) => $query ->where('approved', true), ]) ->get();$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();Both versions express the same intent: return only posts that have approved comments and eager load only approved comments.
With withWhereHas(), the condition is defined once. When it changes, there is no risk of updating whereHas() while leaving a different copy inside with().
What withWhereHas() Actually Does
$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();withWhereHas() applies the same condition in two places:
- it keeps only
Postmodels that have approved comments; - it eager loads only the approved
comments.
After the query runs, $post->comments contains the comments that match the condition.
When whereHas() Is Enough
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();If you only need posts that have approved comments and do not need the comments themselves right now, whereHas() is enough.
When with() Is Enough
$posts = Post::query() ->with([ 'comments' => fn ($query) => $query ->where('approved', true), ]) ->get();If every post should be returned but each post should only eager load approved comments, use with(). The presence of an approved comment does not determine whether the post itself is returned.
Use withWhereHas() When You Need Both
$posts = Post::query() ->withWhereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->get();This is where withWhereHas() communicates the intent best: the relationship affects both which parent models are returned and which related records are loaded.
Keep Intentionally Different Conditions Separate
$posts = Post::query() ->whereHas( 'comments', fn ($query) => $query ->where('approved', true), ) ->with([ 'comments' => fn ($query) => $query ->latest(), ]) ->get();Here the intent is different: a post must have at least one approved comment, but all comments should be loaded from newest to oldest.
Separate whereHas() and with() calls are correct in this case. Do not use withWhereHas() only to make the code shorter when the conditions are not actually the same.
The Shared Condition Should Mean the Same Thing in Both Places
The callback passed to withWhereHas() is used both for relationship filtering and constrained eager loading.
Keep it focused on shared filtering rules such as where() clauses, scopes, and other constraints that should have the same meaning in both query contexts.
Use withWhereHas() when one relationship condition should define both which parent models are returned and which related models are loaded. One intent - one condition.




