Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Best Answer Laravel Package

fof/best-answer

Flarum extension for Q&A-style discussions. Let authors or staff mark a reply as the best answer, highlight it prominently in the thread, and help users find solutions faster. Includes permissions and notifications for selected answers.

View on GitHub
Deep Wiki
Context7

Getting Started

Context first: this is a Flarum 1.x extension, not a Laravel package — there's no service provider you register in config/app.php and no Artisan command of its own. The good news is that Flarum runs on the Illuminate components you already know (illuminate/database, illuminate/events, illuminate/validation, Eloquent models with saved/deleted events), so the mental model maps cleanly:

  • extend.php ≈ your service provider / registration file
  • src/Listeners/ ≈ event subscribers
  • migrations/ ≈ normal Laravel migrations
  • js/ is a separate webpack build (flarum-webpack-config), not Vite/Mix

Install:

composer require fof/best-answer
php flarum cache:clear     # or "Clear cache" in admin

Then enable it in Admin → Extensions, and immediately configure Admin → Permissions for the group(s) that should be able to select an answer (moderators, discussion authors, or both). The most common "it's installed but nothing happens" is simply that neither the group permission nor the extension setting allowing the discussion author to pick was enabled.

Where to look first, in order:

  1. README.md — feature list and the setting toggles.
  2. migrations/ — the schema is small: a best_answer_post_id column on discussions and an is_best_answer flag on posts. Confirm the exact names here before writing any of your own queries.
  3. src/ — serializers (what the API exposes), listeners (what fires when an answer is set), and the notification class.
  4. js/src/ — the "Best Answer" action in the post footer and the badge rendering. This is where you'll extend UI.

First use case: a support/Q&A forum. A user asks a question, a responder replies, and the OP (or a moderator) clicks Best Answer on the reply. The post is highlighted, surfaced near the top of the discussion, indicated in the discussion list, and the responder gets a notification. If you're building a knowledge base or an internal help desk on Flarum, this is the entire "accepted answer" primitive, and you should decide on day one who is allowed to mark it.

Implementation Patterns

1. Reading the data in your own code. Because the relationship lives on the standard Discussion model, you can query it like any Eloquent relation:

use Flarum\Discussion\Discussion;

$answered = Discussion::whereNotNull('best_answer_post_id')
    ->with('bestAnswerPost')
    ->get();

For time-to-answer metrics (the most useful thing you'll build on top of this package), go straight to SQL:

SELECT d.id,
       d.title,
       p.user_id                                   AS answerer_id,
       TIMESTAMPDIFF(HOUR, d.start_time, p.created_at) AS hours_to_answer
FROM discussions d
JOIN posts p ON p.id = d.best_answer_post_id
WHERE d.best_answer_post_id IS NOT NULL;

That gives you a resolution-rate dashboard ("% of questions in tag X that received a best answer in the last 30 days") and lets you target nagging/pinging campaigns at unanswered threads.

2. Headless / API consumers. The discussion resource exposes the best-answer pointer and the post resource exposes the flag, so a docs-site generator or custom frontend can do:

GET /api/discussions?filter[q]=is:unanswered&include=bestAnswerPost,user
GET /api/posts?filter[discussion]=123&sort=-isBestAnswer

Include bestAnswerPost explicitly — Flarum does not ship it by default in every response. If you need extra fields on the discussion (e.g. bestAnswerSetAt), add them with Extend\ApiSerializer on DiscussionSerializer rather than post-processing JSON in the client.

3. Reacting to an answer being set. Two approaches, in order of preference:

  • Listen to the events the package dispatches from src/Listeners/ (check there first — it's the cleanest hook and gives you the actor and post).
  • Fall back to Eloquent model events, which work because Flarum's models extend Illuminate\Database\Eloquent\Model. Register in your own extension's extend.php / boot:
Discussion::saved(function (Discussion $discussion) {
    if ($discussion->wasChanged('best_answer_post_id') && $discussion->best_answer_post_id) {
        // sync to Slack, index in Meilisearch, close a Jira ticket…
    }
});

wasChanged() is the key — a plain saved listener fires on every rename and tag change too.

4. Permission policy refinement. Group-level permissions in Flarum are global, not per-tag. If you want "best answers allowed only in the support tag," don't hack the JS; register a model policy for Discussion in your own extension and gate the ability on the discussion's tags:

(new Extend\Policy())
    ->modelPolicy(Discussion::class, CustomDiscussionPolicy::class),

Then keep the package's own group permission as the coarse filter and let your policy add the tag/tier logic.

5. Frontend customisation. Standard Flarum extender patterns work:

import { extend } from 'flarum/common/extend';
import Post from 'flarum/forum/components/Post';

extend(Post.prototype, 'actionItems', function (items) {
  if (this.attrs.post.isBestAnswer()) {
    // inject a "copy permalink to answer" item, etc.
  }
});

Style overrides go in your own less/forum.less — target the best-answer class on the post/discussion-list item (inspect the rendered DOM rather than guessing; the class names are stable but not documented).

6. Notifications. The extension notifies the post author. If you're using this in an internal tool, check the notification is actually enabled for your user groups, and remember notifications are queued — if your queue worker is down, "it worked but nobody was told" is a queue problem, not an extension bug.

Gotchas and Tips

  • Dangling reference on post deletion. The best answer is a single best_answer_post_id column. If the marked post is deleted (by a user, by moderation, or by a GDPR-style scrub), verify the column is cleared; if not, add a listener for Flarum\Post\Event\Deleted in your own extension that nulls it. This is the #1 thing to check before going live.
  • Visibility leaks. Test explicitly: mark a post as best answer, then hide that post or move the discussion to a restricted tag. You need to confirm a best answer inside a hidden post doesn't get rendered/leaked into a discussion list or API response you didn't intend. Do this test before launch, not after.
  • One answer per discussion only. The schema is a single column — there is no "multiple accepted answers." If your product needs several, you're forking; budget for it rather than discovering it mid-sprint.
  • Don't pair it with another "solved" extension. Two extensions both writing an accepted-answer flag onto discussions produce confusing UI and conflicting badges. Pick one.
  • Assets and cache. After composer update of this or any extension: php flarum cache:clear. If you edited anything under js/, you must rebuild (cd js && npm ci && npm run build, npm run dev while iterating) — a browser hard-refresh will not pick up unbuilt changes, and this is the usual "my edit did nothing."
  • Endpoint discovery. Don't guess the write route if you're integrating externally. Open the browser network tab, click Best Answer, and read the request the frontend makes; then mirror it. Confirm against src/ routes/listeners. The package is small enough to read end to end in ten minutes — do that instead of reverse-engineering.
  • Debugging order of operations: php flarum info (version/compat sanity) → storage/logs/flarum-*.log (PHP side) → browser console + network (JS side) → permissions screen (the silent killer). Nine times out of ten it's the permission toggle.
  • Pin the version and know your exit. 26 stars, zero dependents, MIT, single maintainer: it's a small package carrying a core product feature. Lock a version in composer.json, read release notes before bumping, and be prepared to fork — the codebase is small enough that a fork is a day's work, not a quarter's. (The published last-release date looks odd/future-dated; sanity-check the actual git tags rather than trusting the metadata.)
  • No factories for tests out of the box. If you write integration tests around this behaviour, build the discussion/post fixtures yourself via Flarum's test helpers and assert on best_answer_post_id; don't assume the package ships test scaffolding you can reuse.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource