balajidharma/laravel-reaction
Add like, love, and custom reactions to Laravel Eloquent models with balajidharma/laravel-reaction. Simple traits and APIs let you attach reactions, count them, and check user reactions, making engagement features easy to integrate.
balajidharma/laravel-reaction is a polymorphic "reactions" layer (likes, loves, emoji-style reactions) you attach to any Eloquent model. v2.0.1 is the release that adds Laravel 13 support.
composer require balajidharma/laravel-reaction:^2.0
php artisan vendor:publish --provider="BalajiDharma\LaravelReaction\ReactionServiceProvider"
php artisan migrate
config/reaction.php. This is the single most important file — it defines the allowed reaction types. Every reaction you let users submit must exist here, otherwise it will be rejected/ignored. Decide the list before you write any UI; adding a type later is a config change, removing one is a data migration.Post, Comment, User):
use BalajiDharma\LaravelReaction\Traits\HasReaction;
class Post extends Model
{
use HasReaction;
}
Post. Wire a single POST route that calls the toggle method, return the new summary, and render it. That end-to-end loop (route → toggle → summary → Blade/Livewire partial) is 90% of how this package gets used in real apps.v2 is a major bump from v1.0.6. The headline change is Laravel 13 support, but a semver major means you should read the v2 changelog/upgrade notes before upgrading a live app rather than assuming it's a drop-in.
Toggle vs. explicit react/unreact. The idiomatic pattern is a single toggleReaction call behind a POST endpoint, so the frontend doesn't have to know current state:
Route::post('/posts/{post}/reactions', function (Post $post, Request $request) {
$request->validate(['type' => ['required', 'string']]);
$post->toggleReaction($request->string('type'), $request->user());
return response()->json(['summary' => $post->reactionSummary()]);
})->middleware('auth');
For UI that allows "switch from like to love" (rather than a second click removing), use the explicit add/remove methods and remove the user's other reaction types first.
Keep the reaction set in one place. Drive your Blade/Frontend buttons from config('reaction.reaction_types') and pass it to the view. Never hardcode the emoji list in three places — the config is your source of truth and your server-side validation whitelist.
Aggregates. For a single model, reactionSummary() gives you per-type counts and is fine. For listings, avoid per-card summary queries: eager load reactions and group in PHP, or cache the summary per model (see below). For "most liked posts" style queries, pair the package's relation with withCount('reactions') and order by that.
Livewire / Filament / Inertia. Treat toggle as a single action that returns the fresh summary, then re-render only the reaction bar component. This keeps optimistic UI simple and avoids full page reloads on every tap.
Testing. Use actingAs($user) in feature tests and assert both the database row and the returned summary. Reaction logic is exactly the kind of thing that regresses silently when the config changes.
reaction.config, the reaction silently won't persist (or will throw, depending on the version). When a "like" button does nothing in production, check config:cache first — a cached config that predates your new reaction type is the classic cause.^2.0, and check the Laravel 13 / PHP floor (Laravel 13 raises the PHP minimum). Update your CI matrix and composer.json platform requirements in the same PR as the bump.Relation::enforceMorphMap(), every reactable model must be registered. Otherwise the polymorphic reactable_type will store FQCNs and your data becomes inconsistent the day you rename a class.(reactable_type, reactable_id, user_id, type), catching the constraint violation as an idempotent no-op.can:react gate) on the reactable before calling toggle — otherwise a user can react to a draft post by guessing an ID.Reaction model is publishable/replaceable — if you need extra columns (e.g. an ip or source field), extend it and point the config at your subclass rather than editing vendor code.like to heart later means a data migration across the reactions table. Pick stable slugs up front and label the human-readable text in your frontend.How can I help you explore Laravel packages today?