Hey there! If you're diving into Laravel 12 and want to master the "has many through" relationship, you’ve come to the right place. In this beginner-friendly guide, I’ll walk you through setting up a has many through relationship using Laravel’s Eloquent ORM.
We’ll use a clear example: a Country that has many Posts through Users. By the end, you’ll know how to define models, migrations, and relationships to access related data across tables.

A has many through relationship allows you to access distant related records through an intermediate model. For example:
Country has many Users.User has many Posts.Users, a Country has many Posts.This is useful when you want to retrieve all posts from a country without directly linking the countries and posts tables.
Before we begin, ensure you have:
If you don’t have a project, create one with:
laravel new example-app
We’ll create three tables:
countries: Stores country data.users: Stores user data with a reference to a country.posts: Stores post data with a reference to a user.Run this command to create a migration for the countries table:
php artisan make:migration create_countries_table
Open the generated migration file in database/migrations and update it:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('countries', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('countries');
}
};
Laravel includes a default users migration. Open the migration in database/migrations and modify it to include a country_id foreign key:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->unsignedBigInteger('country_id');
$table->timestamps();
$table->foreign('country_id')->references('id')->on('countries')->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('users');
}
};
Run this command to create a migration for the posts table:
php artisan make:migration create_posts_table
Open the generated migration file and update it:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->string('title');
$table->text('content');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Explanation:
country_id in users links to countries.user_id in posts links to users.onDelete('cascade') ensures related records are deleted if the parent record is removed.Run the migrations to create the tables:
php artisan migrate
Define the Country, User, and Post models and set up their relationships.
Run:
php artisan make:model Country
Open app/Models/Country.php and add the hasMany and hasManyThrough relationships:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Country extends Model
{
protected $fillable = ['name'];
public function users(): HasMany
{
return $this->hasMany(User::class);
}
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class);
}
}
Explanation:
hasMany defines that a Country has many Users.hasManyThrough defines that a Country has many Posts through Users.Open app/Models/User.php and add the belongsTo and hasMany relationships:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
protected $fillable = ['name', 'email', 'password', 'country_id'];
public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}
Explanation:
belongsTo indicates a User belongs to a Country.hasMany indicates a User has many Posts.Run:
php artisan make:model Post
Open app/Models/Post.php and add the belongsTo relationship:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Post extends Model
{
protected $fillable = ['user_id', 'title', 'content'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Explanation: The belongsTo method indicates a Post belongs to a User.
Create a controller to test the has many through relationship and a route to access it.
Run:
php artisan make:controller CountryController
Open app/Http/Controllers/CountryController.php and add:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Country;
use App\Models\User;
use App\Models\Post;
class CountryController extends Controller
{
public function index()
{
// Create a country
$country = Country::create(['name' => 'Canada']);
// Create a user for the country
$user = User::create([
'name' => 'Alice Smith',
'email' => '[email protected]',
'password' => bcrypt('password'),
'country_id' => $country->id,
]);
// Create posts for the user
$user->posts()->createMany([
[
'title' => 'First Post',
'content' => 'Welcome to my blog!',
],
[
'title' => 'Second Post',
'content' => 'Exploring Laravel!',
],
]);
// Retrieve the country with its posts
$countryWithPosts = Country::with('posts')->first();
return response()->json([
'country' => $countryWithPosts->name,
'posts' => $countryWithPosts->posts->map(function ($post) {
return [
'title' => $post->title,
'content' => $post->content,
'author' => $post->user->name,
];
}),
]);
}
}
Explanation:
with('posts') to eager-load the posts through users.Open routes/web.php and add:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\CountryController;
Route::get('/test-relationship', [CountryController::class, 'index'])->name('test.relationship');
Start your Laravel server:
php artisan serve
Visit http://localhost:8000/test-relationship in your browser. You should see a JSON response like:
{
"country": "Canada",
"posts": [
{
"title": "First Post",
"content": "Welcome to my blog!",
"author": "Alice Smith"
},
{
"title": "Second Post",
"content": "Exploring Laravel!",
"author": "Alice Smith"
}
]
}
This confirms the has many through relationship is working!
You can access related data in various ways:
$country = Country::find(1);
$posts = $country->posts; // Returns a collection of Posts
foreach ($posts as $post) {
echo $post->title; // Outputs: First Post, Second Post
}
$post = Post::find(1);
$user = $post->user; // Returns the associated User
$country = $user->country; // Returns the associated Country
echo $country->name; // Outputs: Canada
Create a Blade file resources/views/country.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Laravel 12 Has Many Through Relationship</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h3>Posts from {{ $country->name }}</h3>
<h4>Posts:</h4>
<ul>
@foreach ($country->posts as $post)
<li>
<strong>{{ $post->title }}</strong>: {{ $post->content }} (by {{ $post->user->name }})
</li>
@endforeach
</ul>
</div>
</body>
</html>
Update the CountryController to return the view:
public function index()
{
$country = Country::with('posts')->first();
return view('country', compact('country'));
}
Implementing a has many through relationship in Laravel 12 is a powerful way to access distant related data with Eloquent ORM. This tutorial showed you how to create migrations, define models, and set up a has many through relationship using a Country, Users, and Posts example.
Whether you’re building a global blog or another complex system, this approach simplifies data retrieval. I hope this guide was clear and helps you add has many through relationships to your Laravel projects.
Q1: When should I use a has many through relationship?
A: Use it when you need to access records in a distant table through an intermediate model, like getting all posts in a country through users.
Q2: What’s the difference between hasMany and hasManyThrough?
A: hasMany defines a direct one-to-many relationship (e.g., User has many Posts). hasManyThrough accesses records through an intermediate model (e.g., Country has many Posts through Users).
Q3: Why use onDelete('cascade')?
A: It ensures data integrity by deleting related records (e.g., posts or users) when the parent record (e.g., country or user) is deleted.
Q4: How do I handle cases where a country has no posts?
A: The posts relationship returns an empty collection if no posts exist. Check with @if($country->posts->isEmpty()) in Blade or $country->posts->isEmpty() in PHP.
Q5: Can I add extra conditions to the has many through relationship?
A: Yes! Modify the relationship in the model, e.g.:
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class)->where('posts.created_at', '>=', now()->subMonth());
}
This filters posts from the last month.
You might also like: