Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Like Laravel Package

overtrue/laravel-like

A Laravel package for adding like, unlike, and favorite functionality to Eloquent models. Simple traits let users like any model, check liked status, count likes, and query liked items through clean, expressive APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and migrate

composer require overtrue/laravel-like
php artisan vendor:publish --provider="Overtrue\LaravelLike\LikeServiceProvider"
php artisan migrate

Publishing normally gives you a config/like.php (model bindings, UUID/morph settings) and the likes migration. If you don't see the tags you expect, publish by provider as above, or check the package's README for the current tag names — these have shifted between majors.

First place to look

  1. config/like.php — this is where the Like model class and UUID support are configured. Read it before writing any code; nearly every customization hangs off it.
  2. The published migration — confirms the table name (likes) and the polymorphic columns (likeable_id / likeable_type).
  3. Overtrue\LaravelLike\Traits\Liker and Traits\Likeable — the two traits are the entire public API surface.

Wire up your models

// app/Models/User.php
use Overtrue\LaravelLike\Traits\Liker;

class User extends Authenticatable
{
    use Liker;
}

// app/Models/Post.php
use Overtrue\LaravelLike\Traits\Likeable;

class Post extends Model
{
    use Likeable;
}

First use case

A like/unlike endpoint plus a "did I like this?" flag on the show page:

// routes/web.php
Route::post('/posts/{post}/like', function (Post $post) {
    $post->toggleLike(auth()->user());

    return back();
});

// in a controller / view
$post->likers()->count();          // total likes
auth()->user()->hasLiked($post);   // boolean for the current user

That loop — toggle, count, membership check — covers 90% of what this package is used for, whether you call it "likes", "favorites", "bookmarks", or "upvotes". The semantics are generic; only the table and trait names are like-specific.

Version note for this release

v6.0.0 requires PHP 8.3+ and Laravel 13 only. If you are on Laravel 11 or 12, stay on the ^5.4 line — this is not a version that will install alongside older framework constraints, and there is no compatibility shim.

Implementation Patterns

Controller endpoints (the standard shape)

public function store(Post $post)
{
    $post->like(auth()->user());          // idempotent-ish; see Gotchas

    return response()->json([
        'liked' => true,
        'count' => $post->likers()->count(),
    ]);
}

public function destroy(Post $post)
{
    $post->unlike(auth()->user());

    return response()->json([
        'liked' => false,
        'count' => $post->likers()->count(),
    ]);
}

Return the fresh count from the server rather than incrementing client-side — it keeps the counter honest when two tabs are open.

Toggle for a single button

$post->toggleLike($user);

Use this when the UI is a single optimistic button. Use explicit like/unlike when you have separate buttons and want deterministic state.

Index pages: avoid the N+1

The single biggest day-to-day pattern. hasLiked() per row is a query per row, and likers()->count() per row is another.

$posts = Post::query()
    ->withCount('likers')
    ->paginate(20);

// in the resource
'lkes_count' => $this->likers_count,

For the "liked by me" flag across a collection, do one query and hydrate a set:

$likedIds = Like::query()
    ->where('user_id', auth()->id())
    ->where('likeable_type', (new Post)->getMorphClass())
    ->whereIn('likeable_id', $posts->pluck('id'))
    ->pluck('likeable_id')
    ->flip();

// $likedIds->has($post->id)

That's two queries for the whole page instead of 40.

Custom Like model via config (main extension point)

// config/like.php
'models' => [
    'like' => App\Models\Like::class,
],

// app/Models/Like.php
class Like extends \Overtrue\LaravelLike\Like
{
    protected $with = ['user'];   // or add scopes, casts, soft deletes
}

This is how you add soft deletes, activity-log observers, an is_public flag, or a user() relation eager-load default. Do it before launch — changing the Like model after data exists means a backfill.

Likers list with pagination

$post->likers()->with('profile')->paginate(30);

Resist the urge to ->get() — popular posts will bite you.

Morph maps

If you use Relation::enforceMorphMap([...]), the likeable_type column will store aliases (post instead of App\Models\Post). That's fine and desirable, but it means the stored values differ from a fresh install. Either adopt morph maps on day one or write a migration to rewrite the column when you introduce them.

Blade / API resource convention

Keep the two fields consistent across every resource so the frontend doesn't special-case:

return [
    'id' => $this->id,
    'likes_count' => $this->likers_count ?? $this->likers()->count(),
    'liked_by_me' => $this->liked_by_me,
];

Non-"like" semantics

Because it is just a polymorphic pivot, the same package handles bookmarks and upvotes. If you need two independent concepts (like and bookmark), do not try to multiplex one likes table with a type column bolted on — install the package twice under different table/model names, or use two separate tables. Multiplexing breaks every unique constraint assumption.

Gotchas and Tips

v6 breaks on Laravel ≤12 — plan the upgrade

The release notes are minimal but the constraint change is not: Laravel 13 only, PHP 8.3 minimum, plus a bumped testbench baseline. Practically this means:

  • Check composer.json for php: ^8.3 and laravel/framework: ^13.0 before bumping.
  • Your CI must include an 8.3+ job; if you support 8.2 anywhere, that job will start failing on composer update.
  • Third-party packages that depend on laravel-like and pin ^5.x will block the upgrade. Run composer why-not overtrue/laravel-like 6.0.0 early.
  • Treat this as a framework-upgrade task, not a dependency bump. There's no deprecation path from 5.4 — it's a hard cut.
  • Nothing in the release notes indicates API or schema changes, so code and migrations on the 5.x line should port over unchanged. Verify the published config/migration diff anyway if you re-publish.

Duplicate rows under fast toggling

Two rapid clicks hitting like() concurrently can both pass an existence check and either double-insert or throw a unique-constraint QueryException. Defend with:

try {
    $post->like($user);
} catch (\Illuminate\Database\QueryException $e) {
    // already liked — treat as success
}

or do the write yourself with firstOrCreate, and make sure the migration actually has a unique index on (user_id, likeable_id, likeable_type). If you customised the migration, confirm the index survived.

No built-in counter cache

likers()->count() is a COUNT(*) every time. On a feed page this is fine with withCount, but if you display a total on a hot page, add a likes_count column on the parent model and maintain it in Like model events. Be aware that means drift is now your problem — add a nightly reconciliation command.

Orphaned likes when content is deleted

Polymorphic rows aren't cleaned up by the parent's deletion. Add explicit cleanup:

static::deleted(function (Post $post) {
    Like::where('likeable_type', $post->getMorphClass())
        ->where('likeable_id', $post->id)
        ->delete();
});

...or a scheduled command that sweeps likes whose parent no longer exists. Skipping this leaves you with silently inflated counts if IDs are ever reused, and a table that grows forever.

No self-like protection

Nothing prevents a user from liking their own post. Enforce it in a policy or a form request — it's a product rule, not a package rule.

N+1 creeping back in

The two most common regressions: adding auth()->user()->hasLiked($post) inside a Blade @foreach, and adding a likers accessor to an API resource that's serialized in a collection. Grep for hasLiked( and likers()->count() before any release; both are almost always wrong in a loop.

Testing

No factories ship for the Like model, so write your own or use the relation API directly. In tests:

$this->actingAs($user);
$this->post->like($user);
$this->assertTrue($user->hasLiked($this->post));

With RefreshDatabase the pivots reset cleanly — but if you're asserting exact counts, watch out for tests that like from multiple users without fresh setup.

Config cache

If you change the like model binding or UUID setting in config/like.php, run php artisan config:clear — a cached config will silently keep the old model class and you'll debug the wrong Like instance.

Naming collision

The traits are Liker and Likeable. If your app already has a Likeable trait or interface (common in CMS-style codebases), alias on import: use Overtrue\LaravelLike\Traits\Likeable as HasLikes;.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console