Advertisement

Google Ad Slot: content-top

Polymorphic Many To Many


What is Polymorphic Many To Many?

A Polymorphic Many To Many relationship allows multiple models to share a common relationship with another model using a single pivot table.

👉 In this example:

  • A Playlist can contain many Songs.
  • A Playlist can also contain many Videos.
  • A Playlist can also contain many Podcasts.
  • But instead of creating song_playlist, video_playlist, podcast_playlist, we use one single pivot table: playlistables.
Playlist ↔ Songs  
Playlist ↔ Videos  
Playlist ↔ Podcasts

Flow diagram


Migration:

Use this command to generate a migration for creating a tables:

php artisan make:migration create_polymorphic_many_to_many_tables


Migration File

public function up(): void
{
    // Playlists
    Schema::create('playlists', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->timestamps();
    });

    // Songs
    Schema::create('songs', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('artist')->nullable();
        $table->timestamps();
    });

    // Videos
    Schema::create('videos', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('channel')->nullable();
        $table->timestamps();
    });

    // Podcasts
    Schema::create('podcasts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->string('host')->nullable();
        $table->timestamps();
    });

    // Polymorphic pivot table
    Schema::create('playlistables', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('playlist_id');
        $table->unsignedBigInteger('playlistable_id');
        $table->string('playlistable_type');
        $table->timestamps();

        $table->foreign('playlist_id')->references('id')->on('playlists')->onDelete('cascade');
    });
}

public function down(): void
{
    Schema::dropIfExists('playlistables');
    Schema::dropIfExists('podcasts');
    Schema::dropIfExists('videos');
    Schema::dropIfExists('songs');
    Schema::dropIfExists('playlists');
}


Run Migration

php artisan migrate

Tables

songs table
id title artist created_at updated_at
videos table
id title channel created_at updated_at
podcasts table
id title host created_at updated_at
playlists table
id name created_at updated_at
playlistables table
id playlist_id playlistable_id playlistable_type created_at updated_at

Model:

Generate a model with Artisan:

php artisan make:model Playlist
php artisan make:model Song
php artisan make:model Video
php artisan make:model Podcast

This generates 4 files Playlist.php , Song.php ,Video.php and Podcast.php


Song.php

namespace App\Models;

use App\Models\Playlist;
use Illuminate\Database\Eloquent\Model;

class Song extends Model
{
    protected $fillable = ['title', 'artist'];

    public function playlists()
    {
        return $this->morphToMany(Playlist::class, 'playlistable');
    }
}


Video.php

namespace App\Models;

use App\Models\Playlist;
use Illuminate\Database\Eloquent\Model;

class Video extends Model
{
    protected $fillable = ['title', 'channel'];

    public function playlists()
    {
        return $this->morphToMany(Playlist::class, 'playlistable');
    }
}


Podcast.php

namespace App\Models;

use App\Models\Playlist;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    protected $fillable = ['title', 'host'];

    public function playlists()
    {
        return $this->morphToMany(Playlist::class, 'playlistable');
    }
}


Playlist.php

namespace App\Models;

use App\Models\Song;
use App\Models\Video;
use App\Models\Podcast;
use Illuminate\Database\Eloquent\Model;

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

    public function songs()
    {
        return $this->morphedByMany(Song::class, 'playlistable');
    }

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'playlistable');
    }

    public function podcasts()
    {
        return $this->morphedByMany(Podcast::class, 'playlistable');
    }
}

CRUD Examples

Create

use App\Models\Song;
use App\Models\Video;
use App\Models\Podcast;
use App\Models\Playlist;


// Create Playlist
$playlist = Playlist::create(['name' => 'My Favorites']);

// Create a Song and attach to Playlist
$song = Song::create(['title' => 'Shape of You', 'artist' => 'Ed Sheeran']);
$playlist->songs()->attach($song->id);

// Create a Video and attach to Playlist
$video = Video::create(['title' => 'Laravel Tutorial', 'channel' => 'Code Academy']);
$playlist->videos()->attach($video->id);

// Create a Podcast and attach to Playlist
$podcast = Podcast::create(['title' => 'Tech Talk', 'host' => 'John Doe']);
$playlist->podcasts()->attach($podcast->id);
Output
songs table
id title artist created_at updated_at
1 Shape of You Ed Sheeran 2025-08-28 07:36:16 2025-08-28 07:36:16
videos table
id title channel created_at updated_at
1 Laravel Tutorial Code Academy 2025-08-28 07:36:16 2025-08-28 07:36:16
podcasts table
id title host created_at updated_at
1 Tech Talk John Doe 2025-08-28 07:36:16 2025-08-28 07:36:16
playlists table
id name created_at updated_at
1 My Favorites 2025-08-28 07:36:16 2025-08-28 07:36:16
playlistables table
id playlist_id playlistable_id playlistable_type created_at updated_at
1 1 1 App\Models\Song 2025-08-28 07:36:16 2025-08-28 07:36:16
2 1 1 App\Models\Video 2025-08-28 07:36:16 2025-08-28 07:36:16
3 1 1 App\Models\Podcast 2025-08-28 07:36:16 2025-08-28 07:36:16

Read

use App\Models\Song;
use App\Models\Video;
use App\Models\Podcast;
use App\Models\Playlist;


// Get all Songs in a Playlist
$playlist = Playlist::find(1);
foreach ($playlist->songs as $song) {
    echo $song->title . " by " . $song->artist."<br>";
}

// Get all Videos in a Playlist
$playlist = Playlist::find(1);
foreach ($playlist->videos as $video) {
    echo $video->title . " from " . $video->channel."<br>";
}

// Get all Podcasts in a Playlist
$playlist = Playlist::find(1);
foreach ($playlist->podcasts as $podcast) {
    echo $podcast->title . " hosted by " . $podcast->host."<br>";
}

// Get Playlist from a Song
$song = Song::find(1);
foreach ($song->playlists as $playlist) {
    echo $playlist->name."<br>";
}

Update 

// Update Playlist name
$playlist = Playlist::find(1);
$playlist->update(['name' => 'Updated Playlist']);

// Update Song in Playlist
$song = Song::find(1);
$song->update(['title' => 'Perfect']);

// Sync Songs (Update Attached Songs List)
$playlist = Playlist::find(1);
$playlist->songs()->sync([2, 3]);   // Replace all songs with only songs 2, 3

// Sync Without Detaching (Keep Old Songs + Add New)
$playlist = Playlist::find(1);
$playlist->songs()->syncWithoutDetaching([4, 5]);  // Add songs 4 & 5 without removing existing songs

Delete

// Remove one Song from Playlist
$playlist = Playlist::find(1);
$playlist->songs()->detach(1); // Detach song with id 1

// Remove all Videos from Playlist
$playlist->videos()->detach();

// Delete a Playlist (will not delete songs/videos/podcasts themselves)
$playlist->delete();

Eager loading (optimizes queries)

use App\Models\Song;
use App\Models\Video;
use App\Models\Podcast;
use App\Models\Playlist;


// Get all Songs in a Playlist
$playlist = Playlist::with('songs')->find(1);
foreach ($playlist->songs as $song) {
    echo $song->title . " by " . $song->artist;
}

// Get all Videos in a Playlist
$playlist = Playlist::with('videos')->find(1);
foreach ($playlist->videos as $video) {
    echo $video->title . " from " . $video->channel;
}

// Get all Podcasts in a Playlist
$playlist = Playlist::with('podcasts')->find(1);
foreach ($playlist->podcasts as $podcast) {
    echo $podcast->title . " hosted by " . $podcast->host;
}

// Get Playlist from a Song
$song = Song::with('playlists')->find(1);
foreach ($song->playlists as $playlist) {
    echo $playlist->name;
}

Always use with() when fetching related data in bulk.