Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 Has Many Through Relationship: Explained with Example

๐Ÿ› ๏ธ Laravel Tutorials3 min read

Laravel 13 Has Many Through Relationship: Explained with Example

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 8, 2026
Laravel 13 Has Many Through Relationship: Explained with Example

A has-many-through relationship in Laravel 13 lets a model reach distant records through an intermediate model. If a Country has many Users and a User has many Posts, Country::posts() can return all posts for the country with hasManyThrough(Post::class, User::class), with no manual joins.

What do you need before you start?

  • Laravel 13 on PHP 8.3 or newer
  • Three tables: countries, users (with country_id) and posts (with user_id)

What does the schema look like?

Shell
countries
    id, name

users
    id, country_id, name

posts
    id, user_id, title
PHP
// users migration (excerpt)
$table->foreignId('country_id')->constrained();

// posts migration (excerpt)
$table->foreignId('user_id')->constrained();

How do you define the relationship?

JavaScript
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;

class Country extends Model
{
    public function users()
    {
        return $this->hasMany(User::class);
    }

    public function posts(): HasManyThrough
    {
        return $this->hasManyThrough(Post::class, User::class);
    }
}

The first argument is the final model you want. The second is the intermediate model. Laravel guesses the keys from the names: users.country_id and posts.user_id.

How do you use it?

PHP
$country = Country::find(1);

foreach ($country->posts as $post) {
    echo $post->title;
}

// add constraints like any relationship
$recent = $country->posts()->where('posts.created_at', '>=', now()->subWeek())->get();

Qualify column names with the table (for example posts.created_at) because the generated query joins two tables and some columns exist in both.

How do you customize the keys?

Pass four more arguments when your column names do not follow convention.

PHP
return $this->hasManyThrough(
    Post::class,
    User::class,
    'country_id', // foreign key on users
    'author_id',  // foreign key on posts
    'id',         // local key on countries
    'id'          // local key on users
);

What about has-one-through?

Use hasOneThrough() when you want one related record, such as the latest owner of a car through a mechanic. It takes the same arguments.

JavaScript
public function carOwner(): HasOneThrough
{
    return $this->hasOneThrough(Owner::class, Car::class);
}

How do you avoid N+1 queries?

PHP
$countries = Country::with('posts')->get();

$countries = Country::withCount('posts')->get();

foreach ($countries as $country) {
    echo $country->name.': '.$country->posts_count;
}

How do you verify it works?

JavaScript
public function test_country_has_posts_through_users(): void
{
    $country = Country::factory()->create();
    $user = User::factory()->create(['country_id' => $country->id]);
    Post::factory()->count(3)->create(['user_id' => $user->id]);

    $this->assertCount(3, $country->posts);
}

What are the common mistakes?

  • Wrong argument order: the final model comes first, the intermediate model second
  • Ambiguous column errors because the column name was not table-qualified
  • Trying to attach or save through the relationship. It is read-oriented. Create the posts from the user instead
  • Soft-deleted intermediate rows being ignored or included unexpectedly

FAQ

Can I go through more than two hops?

Not with one built-in relationship. Chain relationships, or write a query with joins.

Does hasManyThrough support eager loading?

Yes. Use with('posts') like any relationship.

Is it faster than looping users?

It runs one joined query instead of one query per user, so it is usually better, but check your indexes on the foreign keys.

References

Article Index & Topic Tags
LaravelDatabaseEloquentRelationships

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.