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

Liking, unliking & toggling

All three verbs are idempotent and return a boolean meaning “is liked now” — like() returns true, unlike() returns false and toggle() returns the resulting state. The facade acts as the signed-in user unless you name an actor:

use RoundlyConsulting\Likes\Facades\Likes;

Likes::like($post);                 // true  — ensure liked (no-op if already liked)
Likes::unlike($post);               // false — ensure not liked (no-op if not liked)
Likes::toggle($post);               // true on like, false on unlike

Likes::actor($user)->like($post);   // as any actor instead of the signed-in user

The trait shorthand

GivesLikes offers the same verbs on the actor — $user->like($post) is Likes::actor($user)->like($post):

$post = Post::find(1);
$user = auth()->user();

$user->like($post);        // true  — ensure liked (no-op if already liked)
$user->unlike($post);      // false — ensure not liked (no-op if not liked)
$user->toggleLike($post);  // true on like, false on unlike

Re-liking restores a previously removed like instead of creating a duplicate row, so each actor keeps a single record per likeable and reaction type. A unique index on the table enforces it, so this also holds under concurrency: a double-click, a second tab or a retried request can never create a second row, and only the request that actually changed something fires Liked / Unliked. Idempotent no-ops write nothing and dispatch no events, so a double-tapped button can’t inflate counts or notifications.

Reaction types

Pick a reaction type with as() — or pass it as the second argument of a trait verb. Without one, default_reaction is used (see Typed reactions):

Likes::as('love')->like($post);
Likes::actor($user)->as('love')->toggle($post);

$user->like($post, 'love');          // the trait takes the type as a second argument
$user->unlike($post, 'love');

In a controller

Because toggle() returns the new state, a heart button needs one call:

use Illuminate\Http\Request;
use RoundlyConsulting\Likes\Facades\Likes;

final class TogglePostLike
{
    public function __invoke(Request $request, Post $post): array
    {
        $liked = Likes::actor($request->user())->toggle($post);

        return ['liked' => $liked, 'count' => Likes::for($post)->count()];
    }
}

Bulk operations

Like or unlike many models at once — handy for imports and “favourite all”:

Likes::likeMany([$post, $comment]);                    // as the resolved actor
Likes::actor($team)->as('love')->unlikeMany([$post]);

$user->likeMany([$post, $comment, $photo]);            // the trait shorthand
$user->unlikeMany([$post, $comment]);
$user->likeMany(Post::whereKey($ids)->get(), 'love');  // any iterable of models, optional type

Bulk calls return nothing. They run the same idempotent like or unlike per item, so already-liked items are skipped and each changed item fires its own events.

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.