SC header logo

Simplifying Many-to-Many Relationships with Laravel Polymorphic Relations




In database architecture, many-to-many relationships often add complexity to our system. One way to tackle this is by using polymorphic relations, which Laravel gracefully handles. This blog post will delve into how you can use Laravel’s polymorphic relations to simplify many-to-many relationships.

We will use the scenario where a user can "like" an item in an online store. To achieve this, we typically need user, item, and pivot tables. This structure can prove challenging, especially when adding more likeable types to the system. Fortunately, with polymorphic relations, we can consolidate all these relationships into one table, simplifying the process of adding new likeable types to the system.

The following illustrations compare a non-polymorphic table structure with a polymorphic table structure:

Non-polymorphic table structure


Polymorphic table structure


The latter structure is simpler and more reusable. When we want to introduce a new likeable type to the system, we only need to create a table for the model and add internal relationships within Laravel. We can forgo the extra steps of creating multiple pivot tables.

In this blog post, we won't delve into the entire process of creating tables, models, migrations, etc. Instead, we'll focus on the key steps to make the transition to polymorphic relationships.

You can find a full working example on our GitHub repository.

Code Examples & Definitions

Before we proceed, it's worth mentioning that we have a working example of the Laravel setup on our GitHub. This provides a practical context to understand the code examples and definitions we'll discuss.

It's good practice to rename the morph relationship to something other than the model namespace - a simple string would suffice. In the boot method of the AppServiceProvider, add a morph map that specifies the class used. By doing this, we decouple the names from the application's internal structure.

    'book' => Book::class,
    'item' => Item::class,
    'sweet' => Sweet::class,

You can read more about this in the Laravel documentation here.


Sweet/Book/Item Model relationships

After creating the necessary models, we must define the polymorphic relationship within them. In this case, we're focusing on the likeables table. This definition must be added to all models that will be "likeable" by the user.

public function likes()
	// omit Model name with the one you are using
    return $this->morphToMany(User::class, 'likeable');

In our case, all models share the same relationship.

You can find the final Sweet, Book, Item models here:

User Model relationships

We also need to define additional relationships for all models that the user can like inside the User model.

// Sweets relation
public function likedSweets(): MorphToMany
    return $this->morphedByMany(Sweet::class, 'likeable');

// Items relation
public function likedItems(): MorphToMany
    return $this->morphedByMany(Item::class, 'likeable');

// Books relation
public function likedBooks(): MorphToMany
    return $this->morphedByMany(Book::class, 'likeable');

To fetch all items liked by the user, we add a hasMany relationship.

// All likes of this user
public function likes(): HasMany
    return $this->hasMany(Likeable::class);

The final User Model can be found at this link.

Likeable Model

The Likeable model should be created with a belongsTo relation to the user. This way, we can retrieve all items liked by a user when needed.

public function user(): BelongsTo
  return $this->belongsTo(User::class);


Sweet/Book/ItemController Controllers

The like controllers will be identical, as their goal is to fetch the template we're using and count the items liked by the user.

Take the BookController as an example, and add the index method inside.

// BookController.php
public function index()
  // Just count number of likes here
  return view('books', [
      'books' => Book::withCount('likes')->get()

You can apply the same logic to all other controllers for likeables - just replace the model name used.

You can find them here:


The Like Controller is responsible for adding or removing likes from items. Create a store method that is triggered via a POST request.

Below is an example of a method responsible for adding the necessary relationships to a pivot table. This method finds the model passed from the request as the model type and tries to find that model_id. If we have defined all relationships between the models correctly, we can use the attach & detach method to add or remove relationships from the pivot table.

// LikeController.php
public function store(LikeableRequest $request): RedirectResponse
  $validated = $request->validated();

  $likeable = $validated['model_type']::findOrFail($validated['model_id']);

  $likeable->likes()->where('user_id', auth()->id())->exists() ?

  return redirect()->back();


Let's take a look at a short example of a form-action view. It passes the model ID and modelType to the controller.

// books.blade.php
<form action="{{ route('like', ['model_type' => \App\Models\Book::class, 'model_id' => $book->id]) }}" method="POST">
  <button type="submit" class="bg-gray-200">
      @if(auth()->user()->likes()->where('likeable_id', $book->id)->where('likeable_type', 'book')->first())

You can find the full code at this link.


Lastly, we have a simple seeder for creating random users and adding polymorphic relationships.

Check the Database\Seeders\UserSeeder here.

public function likeRandomly()
    $users = User::all();
    $sweets = Sweet::factory()->count(10)->create();
    $items = Item::factory()->count(10)->create();
    $books = Book::factory()->count(10)->create();

    foreach ($users as $user) {
        for ($i = 0; $i < random_int(1, 10); $i++) {


By leveraging Laravel's polymorphic relationships, you can simplify your many-to-many relationships. The likable example we explored today shows how polymorphic relations can streamline your code, making it cleaner and more manageable. Enjoy building and keep coding!

Articles You Might Like

Client CTA background image

A new project on the way? We’ve got you covered.