The Art of Morph Relationships in Laravel: A Beginner-Friendly Introduction

07 Aug 2026 | Basile Aelterman

Have you ever found yourself trying to manage multiple relationships between different models in your Laravel application? If so, you're in for a treat! Today we'll explore the concept of morph relationships in Laravel–a neat feature that introduces polymorphism to your database design, making your application more robust and elegant.

What Are Morph Relationships?

Morph relationships (or polymorphic relationships) allow one model to be associated with multiple other models using a single, unified relationship. Imagine having a single database table that can dynamically reference multiple types of parent models. That's exactly what it does in simple terms!

Instead of having to create several pivot tables for each relationship or foreign keys, morph relationships let you streamline your database schema.

There are a total of 3 types of polymorphic relationships:

  1. One-to-One
  2. One-to-Many
  3. Many-to-Many

We'll go over each one in detail, how to create them and how to use them in practice.

One-to-One Polymorphic Relationships: A Picture-Perfect Example

Let's start off with a simple and, most likely, a scenario you've already encountered. Suppose both Users and Businesses need to have an address. Instead of creating separate tables for each, you can create one versatile addresses table that caters to both.

The Setup

Imagine an addressables table structured like this:

  1. id: The unique identifier of the address.
  2. addressable_id: The foreign key of the model that the address belongs to.
  3. addressable_type: The class name of the owning model (in our case, either User or Business).

Here's what the migration for this table might look like:

Schema::create('addressables', function (Blueprint $table) {
$table->id();
$table->morphs('addressable'); // Creates both addressable_id and addressable_type
$table->timestamps();
});

You can add more tables to this migration if you'd like, these will act just like pivot columns.

In Your Models

Next, you define the relationships in your models. In the Address model, you'll add the following:

class Address extends Model
{
/**
* Get the model that own this address.
*
* @return MorphTo
*/
public function addressable(): MorphTo
{
return $this->morphTo();
}
}

And in your User and Business models, you'll use the morphOne method:

// app/Models/User.php
class User extends Authenticatable
{
/**
* Get the address that belongs to the user.
*
* @return MorphOne
*/
public function address(): MorphOne
{
return $this->morphOne(Address::class, 'addressable');
}
}

// app/Models/Bussiness.php
class Business extends Model
{
/**
* Get the address that belongs to the business.
*
* @return MorphOne
*/
public function address(): MorphOne
{
return $this->morphTo(Address::class, 'addressable');
}
}

How It Works in Practice

Let's attach an address to a user:

$user->address()->save($address);

Or to a business:

$business->address()->save($address);

In both cases Laravel handles the magic by storing the correct addressable_id and addressable_type values.

One-to-Many Polymorphic Relationships

Or wait. What if you wanted that both Videos and Posts have multiple Comments? Instead of creating a pivot table for the both, you can use a one-to-many polymorphic relationship.

The Database Design

Your comments table would include something like the following:

  1. id: The unique identifier.
  2. content: The content of the comment in text format.
  3. commentable_id: The ID of the parent model.
  4. commentable_type: the class name of the parent model (in this case, Video or Post).

Your migration should then look something like this:

Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('content');
$table->morphs('commentable');
$table->timestamps();
});

In Your Models

First define the relationship in the Comment model:

// app/Models/Comment.php
class Comment extends Model
{
/**
* Get the model that owns this comment.
*
* @return MorphTo
*/
public function commentable(): MorphTo
{
return $this->morphTo();
}
}

And let's not forgot your Video and Post models:

// app/Models/Video.php
class Video extends Model
{
/**
* Get the comments that belong to this model.
*
* @return MorphMany
*/
public function comments(): MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}

// app/Models/Post.php
class Post extends Model
{
/**
* Get the comments that belong to this model.
*
* @return MorphToMany
*/
public function comments(): MorphToMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}

Using It

Adding a comment is again, pretty easy:

$video->comments()->create($comment);

And for a post:

$post->comments()->create($comment);

Many-to-Many Polymorphic Relationships

Let's consider a scenario where you have Children that can be attached to both Guardians and Schools. Instead of multiple pivot tables, you can have a single, unified pivot table.

The Schema

You'd have a children table for, well, storing the children and a pivot table called childables with the following columns:

  1. child_id: The ID of the child.
  2. childable_id: The ID of the parent model.
  3. childable_type: The class name of the parent model.

A look at what the migration might include:

Schema::create('childables', function (Blueprint $table) {
$table->id();
$table->foreignId('child_id')->constrained()->cascadeOnDelete();
$table->morphs('childable');
$table->timestamps();
});

In Your Models

In your Child model, define the relationships to your School and Guardian models:

class Child extends Model
{
/**
* Get the schools associated with this model.
*
* @return MorphToMany
*/
public function schools(): MorphToMany
{
return $this->morphedByMany(School::class, 'childable');
}

/**
* Get the guardians associated with this model.
*
* @return MorphToMany
*/
public function guardians(): MorphToMany
{
return $this->morphedByMany(Guardian::class, 'childable');
}
}

And of course, do the same for your Guardian and School models:

// app/Models/Guardian.php
class Guardian extends Model
{
/**
* Get the children associated with this model.
*
* @return MorphToMany
*/
public function children(): MorphToMany
{
return $this->morphToMany(Child::class, 'childable');
}
}

// app/Models/School.php
class School extends Model
{
/**
* Get the children associated with this model.
*
* @return MorphToMany
*/
public function children(): MorphToMany
{
return $this->morphToMany(Child::class, 'childable');
}
}

Making It Work

Attaching a child should look like this:

$school->children()->attach($child->id);

And it works the exact same for guardians:

$guardian->children()->attach($child->id);

This makes it extremely ease to extend this by, for example, adding another model that can "own" children.

Wrapping Up

Laravel's morph relationships make the developer experience much better and smoother, and makes your application more robust and maintainable. By using this you avoid unnecessary repetition in your database schema's and simplify your codebase. It can take a while to understand what exactly this does or what effect it has, but it is a must in any Laravel project. Unless you like writing boilerplate, then keep doing what you're doing, I guess.

So the next time you're faced with any model relationships, make sure to make use of this Laravel feature. You'll thank yourself for doing so!


Happy coding!

Basile Aelterman

Basile Aelterman

Software Engineer