Contensio logo

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

  1. A theme includes the reactions widget for a content item.
  2. A visitor clicks a reaction emoji pill. The request is sent via fetch.
  3. If the visitor has no existing reaction, it is added.
  4. If the visitor clicks their active reaction again, it is removed (toggle off).
  5. If the visitor clicks a different reaction, their previous one is replaced.
  6. 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.