Laravel 12 Many-to-Many Eloquent Relationship

Hello! If you're building a Laravel 12 application and need to create a many-to-many relationship between database tables, you're in the right place. In this beginner-friendly guide, I’ll walk you through setting up a many-to-many relationship using Laravel’s Eloquent ORM.

We’ll use a practical example: Users who can have multiple Roles, and Roles that can belong to multiple Users. By the end, you’ll know how to define models, migrations, a pivot table, and manage relationships.

What is a Many-to-Many Relationship?

In a many-to-many relationship, multiple records in one table can be associated with multiple records in another table. For example:

  • A User can have multiple Roles (e.g., admin, editor).
  • A Role can be assigned to multiple Users.

This is managed using a pivot table that connects the two tables (e.g., role_user).

Prerequisites

Before we begin, ensure you have:

  • A Laravel 12 project set up.
  • A database configured (e.g., MySQL, SQLite).
  • Basic knowledge of Laravel models, migrations, and controllers.

If you don’t have a project, create one with:

laravel new example-app

Step 1: Set Up the Database and Migrations

We’ll create three tables:

  • users: Stores user data.
  • roles: Stores role data.
  • role_user: A pivot table linking users and roles.

Create the Users Migration

Laravel includes a default users migration. Check database/migrations to ensure it exists and looks like this:

<?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->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('users');
    }
};

Create the Roles Migration

Run this command to create a migration for the roles table:

php artisan make:migration create_roles_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('roles', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('roles');
    }
};

Create the Pivot Table Migration

Run this command to create a migration for the role_user pivot table:

php artisan make:migration create_role_user_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('role_user', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('user_id');
            $table->unsignedBigInteger('role_id');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('role_user');
    }
};

Explanation:

  • The role_user table has user_id and role_id as foreign keys.
  • onDelete('cascade') ensures that if a user or role is deleted, their pivot table entries are removed.
  • The table name role_user follows Laravel’s convention (alphabetical order of model names).

Run the Migrations

Run the migrations to create the tables:

php artisan migrate

Step 2: Create the Models

Define the User and Role models and set up their many-to-many relationships.

Update the User Model

Open app/Models/User.php and add the belongsToMany relationship:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class User extends Authenticatable
{
    protected $fillable = ['name', 'email', 'password'];

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

Explanation: The belongsToMany method defines that a User can have múltiples Roles.

Create the Role Model

Run this command to create the Role model:

php artisan make:model Role

Open app/Models/Role.php and add the belongsToMany relationship:

<?php

namespace App\Models;

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

class Role extends Model
{
    protected $fillable = ['name'];

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class);
    }
}

Explanation: The belongsToMany method defines that a Role can be assigned to multiple Users.

Step 3: Set Up Routes and Controller

Create a controller to test the many-to-many relationship and a route to access it.

Create a Controller

Run:

php artisan make:controller UserController

Open app/Http/Controllers/UserController.php and add:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Role;

class UserController extends Controller
{
    public function index()
    {
        // Create roles
        $admin = Role::create(['name' => 'Admin']);
        $editor = Role::create(['name' => 'Editor']);

        // Create a user
        $user = User::create([
            'name' => 'John Doe',
            'email' => '[email protected]',
            'password' => bcrypt('password'),
        ]);

        // Assign roles to the user
        $user->roles()->attach([$admin->id, $editor->id]);

        // Retrieve the user with their roles
        $userWithRoles = User::with('roles')->first();

        return response()->json([
            'user' => $userWithRoles->name,
            'email' => $userWithRoles->email,
            'roles' => $userWithRoles->roles->pluck('name'),
        ]);
    }
}

Explanation:

  • Creates two roles (Admin and Editor).
  • Creates a user and assigns both roles using attach.
  • Uses with('roles') to eager-load the roles.
  • Returns a JSON response with the user and their role names.

Define a Route

Open routes/web.php and add:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;

Route::get('/test-relationship', [UserController::class, 'index'])->name('test.relationship');

Step 4: Test the Relationship

Start your Laravel server:

php artisan serve

Visit http://localhost:8000/test-relationship in your browser. You should see a JSON response like:

{
    "user": "John Doe",
    "email": "[email protected]",
    "roles": [
        "Admin",
        "Editor"
    ]
}

This confirms the many-to-many relationship is working!

Step 5: Using the Relationship in Your Application

You can manage and access related data in various ways:

Assigning Roles to a User

$user = User::find(1);
$user->roles()->attach([1, 2]); // Attach roles with IDs 1 and 2
$user->roles()->sync([1]); // Sync to keep only role ID 1, removing others
$user->roles()->detach([1]); // Remove role ID 1

Retrieve a User’s Roles

$user = User::find(1);
$roles = $user->roles; // Returns a collection of Roles
foreach ($roles as $role) {
    echo $role->name; // Outputs: Admin, Editor
}

Retrieve Users with a Specific Role

$role = Role::find(1);
$users = $role->users; // Returns a collection of Users
foreach ($users as $user) {
    echo $user->name; // Outputs: John Doe
}

In a Blade Template

Create a Blade file resources/views/user.blade.php:

<!DOCTYPE html>
<html>
<head>
    <title>Laravel 12 Many-to-Many 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>{{ $user->name }}'s Roles</h3>
        <p><strong>Email:</strong> {{ $user->email }}</p>
        <h4>Roles:</h4>
        <ul>
            @foreach ($user->roles as $role)
                <li>{{ $role->name }}</li>
            @endforeach
        </ul>
    </div>
</body>
</html>

Update the UserController to return the view:

public function index()
{
    $user = User::with('roles')->first();
    return view('user', compact('user'));
}

Conclusion

Setting up a many-to-many relationship in Laravel 12 is straightforward with Eloquent ORM! This tutorial showed you how to create migrations, define models, set up a pivot table, and manage relationships using a Users and Roles example.

Whether you’re building role-based access control or other complex relationships, this approach is flexible and powerful. I hope this guide was easy to follow and helps you implement many-to-many relationships in your Laravel projects.

Frequently Asked Questions (FAQs)

Q1: What is a pivot table, and why is it needed?
A: A pivot table (e.g., role_user) stores the relationships between two tables in a many-to-many relationship. It contains foreign keys linking both tables (e.g., user_id and role_id).

Q2: What’s the difference between attach, sync, and detach?
A: attach adds roles without removing existing ones. sync sets the exact roles, removing any not listed. detach removes specified roles.

Q3: Why use onDelete('cascade') in the pivot table?
A: It ensures that if a user or role is deleted, their entries in the pivot table are also removed, preventing orphaned records.

Q4: How do I add extra fields to the pivot table?
A: Add columns to the pivot table migration (e.g., assigned_at for a timestamp). In the model, use withPivot:

public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)->withPivot('assigned_at');
}

Access it like $user->roles->first()->pivot->assigned_at.

Q5: How do I check if a user has a specific role?
A: Use $user->roles->contains('name', 'Admin') or query: $user->roles()->where('name', 'Admin')->exists().


You might also like:

techsolutionstuff

Techsolutionstuff | The Complete Guide

I'm a software engineer and the founder of techsolutionstuff.com. Hailing from India, I craft articles, tutorials, tricks, and tips to aid developers. Explore Laravel, PHP, MySQL, jQuery, Bootstrap, Node.js, Vue.js, and AngularJS in our tech stack.

RECOMMENDED POSTS

FEATURE POSTS