Laravel 12 Many-to-Many Relationship: A Complete Guide with Examples

Author

Kritim Yantra

Apr 06, 2025

Laravel 12 Many-to-Many Relationship: A Complete Guide with Examples

Many-to-many relationships are common in web applications when multiple records in one table are associated with multiple records in another table. Laravel 12 makes working with these relationships simple through its Eloquent ORM.

Real-World Examples

  • Students and Courses (a student can enroll in many courses, and a course can have many students)
  • Posts and Tags (a post can have many tags, and a tag can belong to many posts)

In this comprehensive guide, we'll cover:

  1. Understanding Many-to-Many Relationships
  2. Database Structure for Many-to-Many
  3. Creating Migrations and Models
  4. Defining Relationships in Eloquent
  5. Attaching, Detaching, and Syncing Relationships
  6. Eager Loading for Optimization
  7. Practical Example: Blog Post Tagging System

1. Understanding Many-to-Many Relationships

A many-to-many relationship requires a pivot table (junction table) that connects the two related tables.

Example Structure

  • posts table: id, title, content
  • tags table: id, name
  • post_tag pivot table: post_id, tag_id

Each post can have multiple tags, and each tag can be assigned to multiple posts.


2. Setting Up the Database

Step 1: Create Laravel 12 Project

composer create-project laravel/laravel many-to-many-demo
cd many-to-many-demo

Step 2: Configure Database

Edit your .env file with database credentials.


3. Creating Migrations and Models

A. Create Posts Table

php artisan make:migration create_posts_table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

B. Create Tags Table

php artisan make:migration create_tags_table
Schema::create('tags', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

C. Create Pivot Table (post_tag)

php artisan make:migration create_post_tag_table
Schema::create('post_tag', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('post_id');
    $table->unsignedBigInteger('tag_id');
    $table->timestamps();

    $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
    $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
});

Run migrations:

php artisan migrate

D. Create Models

php artisan make:model Post
php artisan make:model Tag

4. Defining Many-to-Many Relationships

In Post Model

public function tags()
{
    return $this->belongsToMany(Tag::class);
}

In Tag Model

public function posts()
{
    return $this->belongsToMany(Post::class);
}

5. Working with Relationships

A. Attaching Tags to a Post

$post = Post::find(1);
$post->tags()->attach([1, 2, 3]); // Attach tags with IDs 1, 2, and 3

B. Detaching Tags

$post->tags()->detach(2); // Detach tag with ID 2
$post->tags()->detach(); // Detach all tags

C. Syncing Tags (Maintains exact list)

$post->tags()->sync([1, 3]); // Only tags 1 and 3 will remain attached

D. Toggle Tags

$post->tags()->toggle([1, 2]); // Toggles attachment status

E. Adding Extra Pivot Data

$post->tags()->attach(1, ['created_by' => Auth::id()]);

6. Retrieving Data

A. Get All Tags for a Post

$post = Post::with('tags')->find(1);
foreach ($post->tags as $tag) {
    echo $tag->name;
}

B. Get All Posts for a Tag

$tag = Tag::with('posts')->find(1);
foreach ($tag->posts as $post) {
    echo $post->title;
}

C. Eager Loading

// Without eager loading (N+1 problem)
$posts = Post::all();
foreach ($posts as $post) {
    foreach ($post->tags as $tag) {
        // ...
    }
}

// With eager loading
$posts = Post::with('tags')->get();

7. Practical Example: Blog Post Tagging System

Step 1: Create Sample Data

php artisan tinker

// Create posts
Post::create(['title' => 'First Post', 'content' => 'Content...']);
Post::create(['title' => 'Second Post', 'content' => 'Content...']);

// Create tags
Tag::create(['name' => 'Laravel']);
Tag::create(['name' => 'PHP']);
Tag::create(['name' => 'Web Development']);

// Attach tags
$post = Post::first();
$post->tags()->attach([1, 2]);

Step 2: Create Routes

Route::get('/posts', function () {
    $posts = Post::with('tags')->get();
    return view('posts.index', compact('posts'));
});

Step 3: Create Blade View

<!-- resources/views/posts/index.blade.php -->
@foreach ($posts as $post)
    <h2>{{ $post->title }}</h2>
    <p>Tags:
        @foreach ($post->tags as $tag)
            <span class="badge bg-primary">{{ $tag->name }}</span>
        @endforeach
    </p>
@endforeach

Step 4: Test in Browser

php artisan serve

Visit http://localhost:8000/posts to see posts with their tags.


Conclusion

Key Takeaways:

  1. Many-to-many relationships require a pivot table
  2. Use belongsToMany() in both models
  3. Methods like attach(), detach(), and sync() manage relationships
  4. Eager loading prevents N+1 query problems

Next Steps:

  • Explore polymorphic many-to-many relationships
  • Learn about custom pivot models
  • Implement tag filtering in your blog

🚀 Happy Coding!

📌 Questions? Drop them in the comments below!

Tags

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts