flarum/likes
Flarum Likes adds a familiar like button to every post, letting community members show appreciation with one click. It displays like counts, supports notifications for liked posts, and integrates cleanly with Flarum's permission and discussion systems.
flarum/likes is not a standalone Laravel package — it's the bundled "Likes" extension for Flarum, and it ships as a normal Composer dependency in a default Flarum install (composer show flarum/likes will list it). It runs on Flarum's extender/service-provider model over Illuminate components, so treat it as a reference implementation of a Flarum feature, not a library you use from a generic app.
Where to look first, in this order:
extend.php — every router, serializer attribute, event listener, and notification type the extension registers. This is the map.src/Listener/SaveLikesToDatabase.php — the actual attach/detach on the post_likes pivot, plus the denormalized counter update on posts.likes.src/Event/PostWasLiked.php / PostWasUnliked.php — the two public extension points most people actually use.src/Notification/PostLikedBlueprint.php — how the "X liked your post" alert is addressed and rendered.js/src/forum/components/LikeButton.* and resources/locale/en.yml — frontend wiring and translation keys.Minimum first use case: react to a like in your own extension. Do not edit vendor/flarum/likes; register a listener from your extension's extend.php:
use Flarum\Extend;
use Flarum\Likes\Event\PostWasLiked;
use Flarum\Likes\Event\PostWasUnliked;
return [
(new Extend\Event())
->listen(PostWasLiked::class, function (PostWasLiked $e) {
// $e->post is the liked Post; $e->user is the acting (liking) user.
// Do not notify here — PostLikedBlueprint already does that.
})
->listen(PostWasUnliked::class, function (PostWasUnliked $e) {
// reversal logic
}),
];
Verify the wire format with a real request — GET /api/posts/{id} returns the like count and an actor-relative "has liked" flag on the post payload.
Event-driven side effects (the 80% case). Likes, badges, gamification, webhooks, analytics, Slack pings — all of it belongs in a PostWasLiked listener in your extension. Have the listener implement ShouldQueue and register it via (new Extend\Event())->subscribe(...); the like action is a synchronous HTTP request and a slow listener makes the button feel broken. If your listener needs the post_likes row to already exist, check whether your Flarum version honors listener priority in Extend\Event::listen() — the extension's own persistence listener is registered at load order, not "first".
Extending the API payload. The post serializer already exposes the count and an actor-specific boolean. Add derived fields rather than new endpoints:
use Flarum\Api\Serializer\PostSerializer;
use Flarum\Extend;
return [
(new Extend\ApiSerializer(PostSerializer::class))
->attributes(function (PostSerializer $serializer, $post, array $attributes) {
$attributes['likesLabel'] = $post->likes >= 1000
? round($post->likes / 1000, 1) . 'k'
: (string) $post->likes;
return $attributes;
}),
];
If you genuinely need a list of who liked a post (the default API deliberately does not expose one), add your own relationship field or a dedicated endpoint, and gate it behind an admin/mod permission — liker identity is semi-private data.
Customizing the notification. PostLikedBlueprint is registered with the alert channel by the extension itself. Re-registering the same type from another extension will collide; the safe routes are (a) listen to Flarum\Notification\Event\Sending and mutate/suppress, or (b) bind a decorated blueprint subclass in the container. Never bypass the notification preference check — users who turned off postLiked alerts must stay opted out.
Reporting and backfills. The post_likes pivot (post_id, user_id, created_at) is a perfectly good analytics table:
DB::table('post_likes')
->selectRaw('DATE(created_at) AS day, COUNT(*) AS likes')
->groupBy('day')
->orderBy('day')
->get();
If you import likes from another forum, insert into post_likes and recompute posts.likes in the same transaction — the counter is denormalized and nothing recalculates it for you.
Frontend customization. Extend CommentPost.prototype actionItems, or subclass LikeButton and swap the item, from your extension's js/src/forum/index. New strings go under the flarum-likes.* namespace or your own; don't hardcode English in the component.
vendor/.likes column is a cache, not the truth. Any manual insert/delete into post_likes (seeders, admin tools, user deletion) that skips the counter update produces permanent drift. Audit with SELECT posts.id FROM posts LEFT JOIN post_likes ON ... HAVING posts.likes <> COUNT(*) before blaming the UI.post_likes on user/post deletion. Older migrations did not always; orphaned pivot rows inflate counts after a recount and can break "who liked this" queries.likes. The count is guarded; the like/unlike flow goes through the extension's dedicated routes (currently POST/DELETE /api/posts/{id}/likes — confirm the exact shape in extend.php, it has changed across Flarum versions). Attempting to set the integer via the post update endpoint silently does nothing.Post::whereVisibleTo($actor)) or you'll let users like posts in tags/discussions they can't read.php flarum cache:clear for PHP/extender changes; cd js && npm run dev while developing, npm run build (or php flarum assets:publish) for production. A stale JS bundle is the most common "my LikeButton change does nothing" cause.flarum-likes.forum.post.like_link and friends; overriding one key in your locale file is safer than shadowing the whole component.actingAs() and the JSON:API content type; the CSRF middleware is disabled in Flarum's test case. For pure logic, dispatch PostWasLiked directly — but remember the persistence listener also runs, so wrap DB assertions accordingly.How can I help you explore Laravel packages today?