Content Reactions
Emoji reactions on posts and pages — like, love, fire, and more. One reaction per user or IP. Dashboard widget shows most-reacted content.
About this plugin
Content Reactions - Contensio Plugin
Emoji reactions on posts and pages - 👍 Like, ❤️ Love, 😂 Haha, 😮 Wow, 🔥 Fire, 👏 Clap. One reaction per user or IP per content item. Admins see a breakdown per content item and can reset reactions. Dashboard widget shows the most-reacted content.
Features
- Six emoji reactions - Like, Love, Haha, Wow, Fire, Clap
- Toggle and change - clicking your active reaction removes it; clicking a different one changes it
- Live updates - counts update instantly after reacting via Alpine.js +
fetch, no page reload - Per-user or per-IP - logged-in users tracked by user ID; guests tracked by IP address
- Admin overview - lists all reacted content with per-type counts and totals
- Admin reset - delete all reactions for any content item in one click
- Dashboard widget - "Most Reacted" panel with top content by total reaction count
- Embeddable - drop the widget into any theme partial with one line
How it works
- A theme includes the reactions widget for a content item.
- A visitor clicks a reaction emoji pill. The request is sent via
fetch. - If the visitor has no existing reaction, it is added.
- If the visitor clicks their active reaction again, it is removed (toggle off).
- If the visitor clicks a different reaction, their previous one is replaced.
- Pill counts update instantly. The active reaction is highlighted.
Deduplication
- Logged-in users - unique database constraint on
(content_id, user_id). An upsert updates the existing row. - Guests - checked by IP address. If a row exists for that IP + content item, it is updated.
Installation
Via admin panel
Go to Plugins in your Contensio admin, find Content Reactions, and click Install.
Via Composer
composer require contensio/plugin-reactions
The plugin is auto-discovered. Go to Plugins in the admin and enable it. The migration runs automatically on first enable.
Embedding the widget
@include('reactions::partials.reactions-widget', ['contentId' => $content->id])
The widget is self-contained - it reads the current counts and the visitor's existing reaction on page load, and handles all interaction without any additional setup.
Requirements for the embed to work:
- Alpine.js must be loaded on the page (included in all Contensio default themes).
- A
<meta name="csrf-token">tag must be present in the page<head>(included in all Contensio default themes).
Admin
Reactions list (/account/reactions)
Shows all content items with at least one reaction, ordered by most reactions first. Each row displays:
- Content title and type
- Per-emoji counts as labeled pills (only non-zero counts shown)
- Total reaction count
- Reset button - deletes all reactions for that content item after a confirmation prompt
Dashboard widget
The "Most Reacted" panel appears automatically on the admin dashboard, listing the top 8 content items by total reaction count with a link to the full admin list.
The same widget can also be added to any dashboard layout via the Widget Registry (most-reacted).
Routes
| Method | URL | Description |
|---|---|---|
GET |
/account/reactions |
Admin reactions list |
DELETE |
/account/reactions/{contentId}/reset |
Reset all reactions for a content item |
POST |
/reactions/{contentId} |
Toggle or change a reaction (JSON) |
GET |
/reactions/{contentId} |
Get current reaction summary (JSON) |
API
POST /reactions/{contentId}
Toggle or change the current user's reaction.
Request body:
{ "reaction": "like" }
Valid values: like, love, haha, wow, fire, clap
Response:
{
"success": true,
"your_reaction": "like",
"counts": {
"like": 42,
"love": 18,
"haha": 5,
"wow": 3,
"fire": 11,
"clap": 7
},
"total": 86
}
your_reaction is null if the reaction was toggled off.
GET /reactions/{contentId}
Get the current reaction summary without submitting.
Response:
{
"counts": { "like": 42, "love": 18, ... },
"total": 86,
"your_reaction": null
}
Reaction types
| Key | Emoji | Label |
|---|---|---|
like |
👍 | Like |
love |
❤️ | Love |
haha |
😂 | Haha |
wow |
😮 | Wow |
fire |
🔥 | Fire |
clap |
👏 | Clap |
Database
Creates one table: content_reactions
| Column | Type | Description |
|---|---|---|
id |
bigint | Primary key |
content_id |
bigint | ID of the content item |
user_id |
bigint | Logged-in user ID (nullable for guests) |
ip_address |
varchar(45) | Reactor IP address |
reaction |
varchar(20) | Reaction key (e.g. like, fire) |
created_at |
timestamp | When the reaction was first submitted |
updated_at |
timestamp | When the reaction was last changed |
Unique constraint on (content_id, user_id) prevents duplicate rows for logged-in users at the database level.
Requirements
- PHP 8.2+
- Contensio 2.0+
- Alpine.js (included in all Contensio default themes)
License
AGPL-3.0-or-later - see LICENSE.