Polymorphic Relationships

One relationship, many owners: let comments, images or likes attach to any model.

Polymorphic relations solve a very human modelling problem: the same kind of thing can belong to different parents. A comment might sit on a Post today and a Video tomorrow. Rather than a post_comments and a video_comments table, you keep one comments table with two extra columns: commentable_id and commentable_type.

The shape of it

  • morphTo() lives on the child (the comment) — "I belong to something."
  • morphMany() / morphOne() live on each parent — "I have comments."
  • morphToMany() handles the many-to-many flavour (think tags shared across models).
// Comment.php
public function commentable()
{
    return $this->morphTo();
}

// Post.php
public function comments()
{
    return $this->morphMany(Comment::class, 'commentable');
}

// $post->comments()->create(['body' => 'Nice!']);

The *_type column stores the full class name by default. That is fragile — move or rename a model and old rows break. Laravel lets you register a morph map so the database stores a short, stable alias instead.

Example

Example · php
<?php

namespace App\Providers;

use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Stable, database-friendly aliases instead of raw class names.
        // enforceMorphMap() throws for any unmapped morphable model.
        Relation::enforceMorphMap([
            'post'  => \App\Models\Post::class,
            'video' => \App\Models\Video::class,
            'user'  => \App\Models\User::class,
        ]);
    }
}

// --- The child model ---
namespace App\Models;

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

class Comment extends Model
{
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

// --- Querying across morphs, eager-loaded to avoid N+1 ---
$comments = Comment::query()
    ->with('commentable')          // resolves each parent by its *_type alias
    ->latest()
    ->take(20)
    ->get();

// morphWith lets you eager-load nested relations per concrete type:
$comments->load(['commentable' => function ($morphTo) {
    $morphTo->morphWith([
        \App\Models\Post::class  => ['author'],
        \App\Models\Video::class => ['channel'],
    ]);
}]);

When to use it

  • A comments system uses a polymorphic relationship so a single comments table can hold comments attached to both Post and Video models without separate tables.
  • A tags feature implements morphToMany so the same tag can be attached to Articles, Products, and Events from one tags table.
  • A media library uses a polymorphic hasOne to attach a single featured image to any model type — posts, pages, and events — without duplicating the images table.

More examples

Define a polymorphic one-to-many

The comments table holds commentable_type (e.g. 'App\Models\Post') and commentable_id, enabling a single relationship to serve multiple parent models.

Example · php
// Comment model
class Comment extends Model
{
    public function commentable(): MorphTo
    {
        return $this->morphTo();
    }
}

// Post model
class Post extends Model
{
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

// Video model
class Video extends Model
{
    public function comments(): MorphMany
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Create and retrieve polymorphic comments

Comments are created through the morphMany relationship on each parent, and morphTo() resolves back to the correct parent model automatically.

Example · php
// Attach a comment to a post
$post->comments()->create(['body' => 'Great article!']);

// Attach a comment to a video
$video->comments()->create(['body' => 'Very informative.']);

// Resolve the parent from a comment
$parent = $comment->commentable; // returns Post or Video instance

Polymorphic many-to-many (morphToMany)

morphToMany uses a taggables pivot table with taggable_type and taggable_id columns, letting tags attach to any model type.

Example · php
// Tag model
class Tag extends Model
{
    public function posts(): MorphedByMany
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }
}

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

// Attach tags to a post
$post->tags()->attach([$tag1->id, $tag2->id]);

// All posts for a tag
$tag->posts;

Discussion

  • Be the first to comment on this lesson.