morphToMany is a polymorphic many‑to‑many relationship in Laravel.It allows multiple different models to share a many‑to‑many relationship with another model using a single pivot table.

1. Real-World Example

Scenario: Tags System

  • Posts can have many Tags
  • Videos can have many Tags
  • A Tag can belong to many Posts and Videos

Instead of:

  • post_tag
  • video_tag

We use one pivot table → taggables

2. Database Structure

Main Tables

posts

id
title

videos

id
title

tags

id
name

Pivot Table: taggables

id
tag_id
taggable_id
taggable_type
created_at
updated_at

Column Explanation

Column Purpose
tag_id ID of the tag
taggable_id ID of post or video
taggable_type Model class (Post / Video)

3. Migration Example

Schema::create('taggables', function (Blueprint $table) {
$table->id();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable');
$table->timestamps();
});

morphs('taggable') automatically creates:

  • taggable_id
  • taggable_type

4. Model Relationships

Post Model

class Post extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

Video Model

class Video extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}

Tag Model (Inverse Side)

class Tag extends Model
{
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos() { return $this->morphedByMany(Video::class, 'taggable'); } }

5. How Laravel Knows Which Model?

Laravel stores the fully qualified class name in taggable_type.

Example values:

App\Models\Post
App\Models\Video

This allows Laravel to automatically resolve the relationship.

6. Basic Usage Examples

Attach Tags to a Post

$post = Post::find(1);
$post->tags()->attach([1, 2, 3]);

Detach a Tag

$post->tags()->detach(2);

Sync Tags

$post->tags()->sync([1, 4, 5]);

Get Tags of a Video

$video = Video::find(1);
$tags = $video->tags;

Get All Posts of a Tag

$tag = Tag::find(1);
$posts = $tag->posts;

7. Pivot Table Extra Fields

Pivot Table Example

taggables
- tag_id
- taggable_id
- taggable_type
- assigned_by
- created_at

Model Definition

return $this->morphToMany(Tag::class, 'taggable')
->withPivot('assigned_by')
->withTimestamps();

Access Pivot Data

foreach ($post->tags as $tag) {
echo $tag->pivot->assigned_by;
}

8. Creating Related Records

Create and Attach at Once

$post->tags()->create([
'name' => 'Laravel'
]);

Laravel:

  • Inserts into tags
  • Inserts into taggables

9. Eager Loading (Performance)

Without Eager Loading (Bad ❌)

$posts = Post::all();
foreach ($posts as $post) {
$post->tags;
}

With Eager Loading

$posts = Post::with('tags')->get();

10. Custom Pivot Table Name

return $this->morphToMany(
Tag::class,
'taggable',
'custom_taggables'
);

11. Custom Morph Name

If your columns are:

entity_id
entity_type
return $this->morphToMany(Tag::class, 'entity');

12. Difference: morphToMany vs belongsToMany

Feature morphToMany belongsToMany
Polymorphic ✅ Yes ❌ No
Multiple models ✅ Yes ❌ No
Pivot table Single Multiple
Complexity Higher Lower

Categorized in:

Laravel,