NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Likes for Laravel

Query scopes

HasLikes adds query scopes for popularity lists and filters:

Post::orderByLikesDesc()->limit(10)->get();  // most-liked first (no N+1)
Post::orderByLikes()->get();                 // least-liked first
Post::whereLikedBy($user)->get();            // posts the user liked
Post::whereNotLikedBy($user)->get();         // posts the user has not liked
Post::withLikesCount()->get();               // hydrate a likes_count attribute

Every scope takes an optional reaction type as its last argument and composes with the rest of your query. A typed count lands in its own attribute — likes_{type}_count — so it is never mistaken for the all-reactions total:

Post::orderByLikesDesc('love')->limit(10)->get();  // most-loved first
Post::whereLikedBy($user, 'love')->get();          // posts the user loved
Post::withLikesCount('love')->get();               // hydrate likes_love_count

Post::query()
    ->whereNotLikedBy($user)
    ->orderByLikesDesc()
    ->paginate(20);

How the scopes behave

  • Without a type, the scopes match any reaction — whereLikedBy($user) finds posts the user reacted to in any way. The instance checks (isLikedBy(), hasLiked()) default to default_reaction instead.
  • orderByLikes() and orderByLikesDesc() hydrate likes_count (or likes_{type}_count for a typed sort) as they sort, so each row carries its count without another query. withLikesCount() and withLikesCount('love') can be combined on one query.
  • Soft-deleted (unliked) rows never count.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.