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
<?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.
// 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.
// 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 instancePolymorphic many-to-many (morphToMany)
morphToMany uses a taggables pivot table with taggable_type and taggable_id columns, letting tags attach to any model type.
// 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