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 Likeable Laravel Package

cybercog/laravel-likeable

Add like and dislike functionality to Laravel Eloquent models via a polymorphic many-to-many relationship. Includes reusable traits, query scopes, and events, letting users quickly like or dislike posts, comments, and any other model.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and get a likeable model wired up in a few minutes:

composer require cybercog/laravel-likeable
php artisan vendor:publish --provider="Cog\Likeable\LikeableServiceProvider"
php artisan migrate

The service provider is auto-discovered. Publishing gives you config/likeable.php (model bindings, table names, UUID/ID settings) and the likes migration — read the config file first, since it is the single place that decides which Like model class and which table the package uses.

Then add the two traits:

use Cog\Likeable\Traits\Likeable;   // on the thing being liked
use Cog\Likeable\Traits\Liker;      // on the User

class Post extends Model
{
    use Likeable;
}

class User extends Authenticatable
{
    use Liker;
}

First working use case — a toggle endpoint plus an index that shows whether the current user liked each post:

// routes
Route::post('posts/{post}/like', fn (Post $post) => [
    'liked' => auth()->user()->toggleLike($post),
]);

// controller
$posts = Post::withCount('likes')->paginate();

// blade
@if (auth()->user()->liked($post)) Liked @endif

Start with like(), unlike(), toggleLike(), liked($model), isLikedBy($user), and the likes() morph-many relation. Those cover ~90% of real usage; the rest of the API (scopes, counts, custom models) is for scale.

Implementation Patterns

Keep the check and the write on the right side of the traits. Liker methods take the model ($user->like($post)), Likeable methods read from the model ($post->isLikedBy($user)). Mixing them up is the most common source of "method not found" during integration.

Feed pages: eager load, then check (this is what 3.1.0 optimises).

$posts = Post::with('likes')->withCount('likes')->paginate(20);

// Each of these is now resolved from the loaded collection — no per-row query.
$posts->each(fn ($post) => $post->isLikedBy($user));

Before 3.1.0 this pattern was still N+1: loading likes didn't help because the check always queried. Now the check consults the loaded relation first, so with('likes') on a feed is genuinely worth doing. Pair it with withCount('likes') so you get the counter without a second relation.

Constrain the eager load when like volume is high. Loading every like on a viral post just to answer "did this user like it" is wasteful. Define a narrowed relation and use it for the UI:

public function likesByMe(): MorphMany
{
    return $this->morphMany(Like::class, 'likeable')
        ->where('user_id', auth()->id());
}

Use with('likes') when the fast path matters and volume is small; use withCount + a constrained relation when posts are popular.

Filtering lists by like state — use the package scopes rather than joining the likes table by hand:

Post::whereLikedBy($user)->paginate();     // liked by this user
Post::whereNotLikedBy($user)->paginate();  // not yet liked

Expose it in API resources as explicit fields, so clients don't need to know about the relation:

return [
    'id'          => $this->id,
    'likes_count' => $this->likes_count,
    'is_liked'    => $this->isLikedBy(auth()->user()),
];

Prefer a single toggle endpoint over separate like/unlike POSTs. It keeps the client simpler and removes a class of "double-click sends two likes" bugs.

Custom models go through config, not through the trait. If you need extra columns on the like record, extend the package Like model and rebind it in config/likeable.php — don't edit the published migration in place on an existing install.

Events/observers: if you need notifications ("X liked your post"), hook the model events on your own Like model rather than scattering Notification::send in controllers. That keeps the like path the single source of truth.

Gotchas and Tips

  • Stale relations after a write. Because 3.1.0 now trusts the loaded relation, a check performed after like()/unlike() in the same request can return the pre-write answer if the relation was loaded earlier. Call $post->load('likes') / $post->unsetRelation('likes') after mutating, or re-fetch. This is the single most likely regression to hit when upgrading from 3.0.x.
  • Upgrading 3.0.1 → 3.1.0 is drop-in. No migrations, no config changes, no API signatures changed. The only behavioural delta is the eager-loaded fast path, so the risk is confined to code that (a) eager loads likes and (b) also mutates like state in the same request.
  • with('likes') loads all likes. On high-traffic models this can be hundreds of rows per item. Benchmark before adding it to a feed; withCount alone is often enough.
  • Duplicate likes. A check-then-write toggle is not atomic. Add a unique index on (likeable_type, likeable_id, user_id) in a follow-up migration so concurrent requests fail loudly instead of double-inserting.
  • Guest users. liked($model)/isLikedBy($user) expect a real user instance — guard with auth()->check() in Blade and policies, or the null user will surface as a null-constraint error.
  • Morph types are stored as class strings by default. Renaming or namespacing a likeable model orphans its likes. Call Relation::enforceMorphMap([...]) early, and add it before you ship, not after.
  • Counters are not maintained for you. likes_count comes from withCount('likes'); don't expect a column on posts to update itself. If you need one for sorting, keep it in sync via a model observer.
  • Deleting a user leaves likes behind unless your foreign key cascades or you clean up in a deleting observer. Decide explicitly.
  • Debugging "why is this false?" Check in this order: is the liker model the same class as the one used to check; is the morph map enforced consistently; is a stale likes relation loaded on the instance. toSql() on $post->likes() is faster than guessing.
  • Extension point: the config file's model bindings are the sanctioned way to swap in custom Like/Liker/Likeable implementations — anything you'd otherwise be tempted to monkey-patch belongs there.
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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager