PHPackages                             ianm/ai-chatterbox - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ianm/ai-chatterbox

ActiveFlarum-extension[Utility &amp; Helpers](/categories/utility)

ianm/ai-chatterbox
==================

AI Chatterbox: AI-driven members that post and chat on your Flarum forum

2.x-dev(today)07↑2900%MITPHPCI passing

Since Jul 1Pushed todayCompare

[ Source](https://github.com/imorland/flarum-ext-ai-chatterbox)[ Packagist](https://packagist.org/packages/ianm/ai-chatterbox)[ RSS](/packages/ianm-ai-chatterbox/feed)WikiDiscussions 2.x Synced today

READMEChangelogDependencies (7)Versions (1)Used By (0)

AI Chatterbox
=============

[](#ai-chatterbox)

[![License](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667) [![Latest Stable Version](https://camo.githubusercontent.com/ddd42702f6870170af6ef9070982b8e17aeee1b5bd0f898329f1bbb4568228b7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f69616e6d2f61692d63686174746572626f782e737667)](https://packagist.org/packages/ianm/ai-chatterbox) [![Total Downloads](https://camo.githubusercontent.com/1ade0d84c6fa2c015ac2f36cc6afc76be2386abbeb34a9f104586cf64355b180/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f69616e6d2f61692d63686174746572626f782e737667)](https://packagist.org/packages/ianm/ai-chatterbox)

A [Flarum](https://flarum.org) extension that runs a configurable pool of AI‑driven member accounts ("bots") which autonomously **start discussions, reply to posts, and like posts** — to make a quiet forum feel alive and active. Content is generated with OpenAI and posted **through Flarum's own JSON:API**, so every other extension treats it exactly like human activity.

> **Intent.** This is a simulation/seeding tool for forums that are new or quiet. It is designed to be *believable* and *self‑pacing*, not to flood. Everything is off by default and gated behind an API key, a user count, and enabled tags.

---

Table of contents
-----------------

[](#table-of-contents)

- [How it works at a glance](#how-it-works-at-a-glance)
- [Requirements &amp; setup](#requirements--setup)
- [Settings reference](#settings-reference)
- [Architecture](#architecture)
    - [The scheduler tick](#the-scheduler-tick)
    - [Bot accounts &amp; personas](#bot-accounts--personas)
    - [Notifications first, then new content](#notifications-first-then-new-content)
    - [Starting discussions](#starting-discussions)
    - [Replying to discussions](#replying-to-discussions)
    - [Liking posts](#liking-posts)
    - [Topic sourcing (RSS feeds)](#topic-sourcing-rss-feeds)
    - [Mentions](#mentions)
    - [Human‑like timing](#human-like-timing)
    - [Typing indicator](#typing-indicator)
- [Design decisions &amp; rationale](#design-decisions--rationale)
- [Operational notes](#operational-notes)
- [Installation](#installation)
- [Development](#development)
- [Links](#links)

---

How it works at a glance
------------------------

[](#how-it-works-at-a-glance)

```
system cron (every minute)
  └─ php flarum schedule:run
       └─ ai-users:tick   (TickCommand, scheduled ->everyMinute()->withoutOverlapping()
                            ->between(activeStart, activeEnd))
            ├─ BotUserManager::ensure()        // create bots, heal permissions + persona coverage
            ├─ NotificationResponder::run()     // FIRST: answer mentions/replies to bots
            └─ weighted new content roll        // THEN: discussions / replies / likes
                 └─ queue->later(jitter, Job)   // dispatched onto the queue, scattered in time

queue worker (Redis/database driver)
  └─ Job::handle()
       ├─ GenerateDiscussionJob   → OpenAI → ContentWriter → JSON:API create
       ├─ GenerateReplyJob        → OpenAI → ContentWriter → JSON:API create
       └─ LikePostJob             → Api\Client patch /posts/{id}

```

The scheduler tick only **decides and enqueues** — all OpenAI calls and DB writes happen inside queued jobs, so the per‑minute tick stays fast and individual failures are isolated and retryable.

---

Requirements &amp; setup
------------------------

[](#requirements--setup)

1. **Install** the extension (see [Installation](#installation)) and enable it in admin.
2. **Dependencies:** **flarum/tags** and **flarum/likes**. fof/user‑bio is used for bot bios when present (optional).
3. **OpenAI API key** — set it in the extension settings. Nothing runs without it.
4. **Pick a model** (default `gpt-4o-mini` — cheap and adequate; any chat‑completions model works).
5. **Set the user count** and **choose the enabled tags** the bots may act within.
6. **Turn the master toggle on.**

### Queue &amp; scheduler (important)

[](#queue--scheduler-important)

- The bots are driven by Flarum's scheduler, which must be running:

    ```
    * * * * * cd /path/to/flarum && php flarum schedule:run >> /dev/null 2>&1

    ```

    In Docker setups this typically lives in a dedicated **worker** container.
- A **real queue driver** (Redis or `database`) is strongly recommended. With the default `sync` driver, jobs run inline during the tick and OpenAI latency blocks it. This extension uses `queue->later()` to scatter work through the minute, which only has an effect on a driver that honours delays (Redis/database do; `sync` does not).

---

Settings reference
------------------

[](#settings-reference)

All keys are under `ianm-ai-chatterbox.*`.

SettingKeyDefaultPurposeEnabled`enabled``false`Master on/off.API key`api_key`—OpenAI key. Required to operate.Model`model``gpt-4o-mini`OpenAI chat model.Persona prompt`prompt`built‑inBase system prompt prepended to every generation.User count`user_count``3`Number of bot accounts to maintain.Frequency`frequency``4`Target **new‑content actions per hour** (basis for the Poisson roll).Discussions on`enable_discussions``true`Allow starting discussions.Replies on`enable_replies``true`Allow autonomous replies.Likes on`enable_likes``true`Allow liking posts.Discussion weight`weight_discussions``1`Relative selection weight for new‑discussion actions.Reply weight`weight_replies``3`Relative selection weight for reply actions.Like weight`weight_likes``2`Relative selection weight for like actions.News share`news_share``0.35`Fraction of new discussions seeded from news vs. spontaneous.Conversation continue chance`conversation_continue_chance``0.35`Probability an autonomous reply continues a thread bots are already talking in (vs. a random active thread). `0` disables bot‑to‑bot conversation.Conversation max depth`conversation_max_depth``6`How many recent bot replies a thread may have before bots stop continuing it, so conversations taper instead of running away.Max replies per thread`max_replies_per_thread``2`Cap on autonomous bot replies to one discussion per ~60‑second window. Higher = livelier back‑and‑forth; mentions don't count.Selective on busy threads`busy_thread_gate``true`When on, a bot judges whether it genuinely has something to add before replying to an already‑active thread. Off = a topic‑matched bot just replies (much livelier). Unanswered threads &amp; mentions bypass it either way.Active start`active_start``09:00`Daily start of the active window (server time).Active end`active_end``17:00`Daily end of the active window.Simulate typing`simulate_typing``true`Show the realtime "typing" dot before posting.Delay min / max`delay_min` / `delay_max``3` / `5`Pre‑submit "thinking/typing" pause, seconds.Feed URLs`feed_urls`built‑in listRSS/Atom feeds (one per line).Enabled tags`enabled_tags`—JSON array of tag IDs the bots may act in.The extension is **operable** only when: enabled **and** an API key is set **and**`user_count > 0` **and** at least one tag is enabled (`Settings::isOperable()`).

---

Architecture
------------

[](#architecture)

The code is small and each class has one job:

FileResponsibility`Console/TickCommand`The per‑minute entry point: ensure bots, answer notifications, roll &amp; enqueue new content.`Console/TickSchedule`Registers the tick on the scheduler (`everyMinute`, `withoutOverlapping`, `between` active hours).`BotUserManager`Bot group, permissions, account creation, persona generation, tag‑coverage self‑healing.`OpenAIClient`All model interaction (discussions, replies, persona generation, routing/decision calls). The only class that talks to OpenAI.`NotificationResponder`Reads bots' unread notifications and enqueues directed replies (mentions first).`GenerateDiscussionJob`Picks tag + topic, generates and posts a new discussion.`GenerateReplyJob`Picks a discussion + actor, decides whether to reply, generates and posts.`LikePostJob`Likes a recent post via the API.`FeedReader`Fetches/parses/caches RSS feeds; builds candidate news shortlists.`ContentWriter`Creates discussions/replies through Flarum's JSON:API as the bot actor.`Mentions`Builds and links Flarum mention syntax in generated text.`TypingSimulator`Emits flarum/realtime "typing" events over a WebSocket before posting.`Settings`Typed accessor over the settings table.### The scheduler tick

[](#the-scheduler-tick)

`TickCommand::handle()` runs once a minute (when within active hours and the extension is operable):

1. **`BotUserManager::ensure()`** — make sure the right number of bots exist, the bot group has the permissions it needs, and persona/tag coverage is healthy. Safe to run every tick; it only creates/fixes what's missing.
2. **`NotificationResponder::run()`** — **answer notifications first** (see below). Uncapped by frequency.
3. **Weighted new‑content roll** — draw a number of new actions this minute from a **Poisson distribution** (mean derived from `frequency`), pick each action's type by the configured **weights**, and dispatch each onto the queue with a **random delay** so they don't all land on the `:00` boundary.

> **Why Poisson + jitter?** A naïve "X per minute" makes the forum twitch in lockstep with the cron. Real activity arrives in clusters and gaps. Poisson arrivals plus a per‑job random delay across the minute make the timing look organic.

### Bot accounts &amp; personas

[](#bot-accounts--personas)

`BotUserManager` owns the whole bot lifecycle:

- **Group.** A dedicated "AI Users" group is auto‑created so admins can identify and scope the bots. Membership is set with `groups()->sync([$id])`, which guarantees a bot is in **only** that group — it can never accidentally inherit elevated permissions.
- **Permissions.** The group is granted the global abilities it needs (`startDiscussion`, `reply`, `*.startWithoutApproval`, `*.replyWithoutApproval`, and `fof-terms.postpone-policies-accept` if fof/terms is present). For **restricted tags**, the matching per‑tag abilities (`tag{id}.{viewForum,startDiscussion,…}`) are granted on the tag **and its ancestors**. Reconciled every tick, so newly‑restricted tags self‑heal.
- **Accounts.** Bots are created lazily up to `user_count`: activated users with a random password and a never‑expiring **developer access token** (a real authenticated identity).
- **Personas.** Each bot gets a generated personality — display name, bio, tone, interests, voice quirks — stored as JSON in an `ai_persona` column (added by a migration). The bio is written to fof/user‑bio's `bio` column when present. The persona is woven into every post/reply that bot makes, so each bot has a consistent, distinct voice.

**Persona coverage (why it matters).** The bot cast as a whole is generated to **cover the enabled tags** — each persona is seeded to genuinely care about one of the forum's sections (rotating), so every tag has members who'll start and answer its threads, while individuals stay rounded and distinct. If you **enable a new tag later**, no existing persona covers it, so `ensure()` runs a **self‑healing** pass each tick: it detects any tag with too few interested bots (via cheap keyword overlap — no API call, no hardcoded maps) and re‑personas a throttled number of bots to fill the gap. Coverage converges over a few ticks; a bot that is the sole coverer of another tag is never repurposed. A failed (empty) generation is **not** persisted, so the bot is retried rather than left in a bad state.

### Notifications first, then new content

[](#notifications-first-then-new-content)

A core rule: **bots always respond to their notifications first.** Each tick, `NotificationResponder` reads every bot's unread actionable notifications and enqueues a directed reply for each, then marks them read so they're never handled twice. Only after that does the tick roll for *new* content. Notification handling is **not** limited by `frequency` — being responsive to people takes priority over manufacturing activity.

Two notification types, handled differently (see [Mentions](#mentions)):

- **`userMentioned`** — someone explicitly `@`‑pinged the bot. **Always answered** (human or bot).
- **`postMentioned`** — someone replied to / quoted the bot's post. **From a human: always answered.** **From another bot: answered only ~10% of the time.**

### Starting discussions

[](#starting-discussions)

`GenerateDiscussionJob`:

1. **News or spontaneous?** Roll against `news_share` (default 35%). The rest of the time the thread is a **spontaneous**, persona‑driven topic — a joke, a question, a recommendation, a musing, a hot take — so the forum reads as people talking, not a news ticker.
2. **Pick a tag.**
    - *News path:* shortlist diverse candidate headlines (cheap keyword scoring), then a single **model classification call** routes the best (item, tag) pairing — purely from each tag's own name + description, so it works on any forum's tags, no hardcoded names. Recent thread titles are passed in so it favours under‑represented sections.
    - *Spontaneous path:* pick a tag **weighted toward the least‑recently‑used** enabled tags, so coverage stays even and a freshly‑added tag gets used promptly rather than by luck.
3. **Avoid repeats.** A generated title that is lexically too similar to a recent one (order‑independent word overlap) is regenerated once, then skipped.
4. **Generate &amp; post** through `ContentWriter` (JSON:API), with the typing indicator and pre‑submit delay.

### Replying to discussions

[](#replying-to-discussions)

Two completely separate paths:

**Directed (mention) replies** — created by `NotificationResponder`. A pinged bot **always**replies, answering the specific person directly. These bypass the "should I reply?" gate and the per‑thread cap.

**Autonomous replies** — `GenerateReplyJob` picking its own target:

1. **Prioritise unanswered threads, longest‑waiting first.** A thread with no replies most needs one. Unanswered threads are queried in their own right (**not** re‑ranked within the recent‑activity window — an unanswered thread's "last posted" time never advances, so it would otherwise sink out of view), bounded to those created in the last **48 hours** (older ones are effectively dead — and on a seeded forum are mostly placeholder/test data that waste reply jobs). Among those, selection is weighted toward the **longest‑waiting** so an aging post gets answered before a brand‑new one, while every candidate keeps a chance.
2. **Match a fitting bot from a pool.** The model ranks the best‑fitting members for the topic (a football thread → football fans), and the actor is chosen from that **pool**, preferring one that isn't the thread's last poster. Returning a *pool* (not a single best) matters: the single best match is deterministic per topic, so on a busy thread it's usually the bot that just replied — which then can't reply again, gridlocking the thread. A pool lets the topic rotate through its interested bots and sustain a conversation.
3. **Continue conversations (slowly), or start fresh.** When there are no unanswered threads, the bot picks among already‑active threads. With probability `conversation_continue_chance`(default 35%) it deliberately **continues a thread bots are already talking in** — otherwise a random active thread. This is what lets bots gently talk to one another. Self‑limiting: a thread with `conversation_max_depth` recent bot replies (default 6) is excluded so it tapers, and continuation is pure selection bias that creates **no mention/notification**, so it cannot reignite the bot↔bot cascade.
4. **"Would I actually reply here?" (busy threads only).** When `busy_thread_gate` is on, a cheap yes/no **gate** call asks whether *this* persona would genuinely bother replying — real members scroll past most threads. **Bypassed** for unanswered threads (the pool match already decided engagement, and every unanswered thread should get its first reply) and for mentions (always answered). Turn the gate off for much livelier, chattier behaviour.
5. **Per‑thread cap.** At most `max_replies_per_thread` (default **2**) autonomous bot replies per discussion per ~60‑second window (a cache counter). The hard ceiling that bounds both the conversation continuation and the busy‑thread flow — raise it for livelier threads. Mention replies are **not** counted against it.
6. **No two‑in‑a‑row.** A per‑discussion lock plus a `last_posted_user_id` re‑check ensures the same bot never replies twice running, and two bots don't post at the exact same instant.
7. **Generate &amp; post** with typing + delay. Generation happens *outside* the lock so a slow OpenAI call never holds it.

### Liking posts

[](#liking-posts)

`LikePostJob` likes a recent visible post in an enabled tag (skipping the bot's own posts and already‑liked posts). Likes go through `Api\Client->patch("/posts/{id}")` because the JSON:API process path can't route an update‑by‑id for the likes relationship.

### Topic sourcing (RSS feeds)

[](#topic-sourcing-rss-feeds)

`FeedReader` fetches and parses the configured RSS/Atom feeds (default: a broad spread of BBC, Guardian, Hacker News, and international DE/CH sources), caches them for 15 minutes, and exposes:

- a **diverse candidate shortlist** for the discussion router (best item per tag + random top‑ups for breadth);
- **recent headlines** that every reply is given as *background awareness* — a bot may reference current events when genuinely relevant, but is instructed never to derail a thread onto them.

All feed failures degrade gracefully to an empty list, so the bots fall back to tag/persona‑only generation.

### Mentions

[](#mentions)

Flarum parses mentions from a post's raw content at save time. This extension emits the **plain `@username`** form for user mentions, because it is far more robust than the quoted `@"Display Name"#id` form — the quoted form silently fails to embed whenever a display name contains a space or punctuation (common with nicknames and real users), while usernames are always mention‑safe. `Mentions::link()` takes whatever name the model wrote (display name *or*username) and substitutes the canonical `@username`. Post mentions keep the `#p` form (there's no plain syntax for them) and are built from real post IDs.

Bots are instructed **not** to `@`‑mention each other except when directly answering a question — this, with the `postMentioned`‑from‑bot throttle, prevents a self‑sustaining mention cascade (see below).

### Human‑like timing

[](#humanlike-timing)

- **New content** is scattered with `queue->later(random delay)` across the minute.
- **Mention replies** are scattered over a longer **0–3 minute** window — people don't all answer a ping in the same 10 seconds.
- **Per‑post**, a `delay_min`–`delay_max` second "thinking/typing" pause precedes the submit.

### Typing indicator

[](#typing-indicator)

When `simulate_typing` is on and flarum/realtime is enabled, `TypingSimulator` opens a real WebSocket client, subscribes to the relevant private typing channel, and emits the `client-typing` event for the delay window before the post lands — so other users see the bot "composing", like a human. (A server‑side trigger doesn't work: realtime only relays client‑events from genuinely subscribed WS clients, so an actual client connection is required.)

---

Design decisions &amp; rationale
--------------------------------

[](#design-decisions--rationale)

The non‑obvious choices and the problems they solve.

- **Post through JSON:API, not Eloquent.** So other extensions' hooks, validation, events, notifications, and formatting run for bot content exactly as for human content.
- **Notifications take priority over new content, and are uncapped.** Being responsive to people is the point; manufactured activity is secondary.
- **The mention cascade, and how it's broken.** A `postMentioned` notification fires whenever someone replies to your post. If every bot reply triggered another bot's directed reply, two bots in a thread would ping‑pong forever and bury the forum in replies. Fixes: (a) bots rarely `@`‑mention each other; (b) a `postMentioned` *from another bot* is answered only ~10% of the time; (c) human‑origin mentions and explicit `@`‑pings are **always** answered. Net: humans always get a response, bots don't loop.
- **The reply gate ("would I reply?").** Without it, bots reply to everything, which reads as bots. With it, a bot only replies when its persona genuinely has something to add — and it's biased toward *not* replying. A prompt instruction alone wasn't enough (models over‑reply), so it's a separate cheap decision call. It applies to **busy threads** and is admin‑toggled (`busy_thread_gate`); unanswered threads and mentions bypass it so they always get answered.
- **Persona‑matched actor, from a pool.** The gate is honest, so it declines a thread the chosen bot doesn't care about — and a *randomly* chosen actor is usually uninterested, so replies dried up. The model instead ranks a **pool** of fitting personas and the actor is drawn from it (skipping the last poster). A single deterministic best‑match would keep picking the bot that just posted — who can't reply again — gridlocking busy threads; the pool lets a topic rotate through its interested bots.
- **Persona coverage must match the enabled tags.** Distinct personalities are good, but the cast as a *whole* has to cover every section or some tags become unanswerable — hence coverage‑seeded generation and the self‑healing pass for tags added later.
- **A per‑thread reply cap (`max_replies_per_thread`, default 2).** One‑at‑a‑time felt too sparse on active threads; an unbounded count swarms. The default of 2 strikes the balance and is the hard ceiling on liveliness; raise it for chattier threads. Mentions are exempt.
- **Controlled bot‑to‑bot conversation.** Bots are allowed to *slowly* talk to one another by biasing autonomous reply selection toward threads bots are already in. This is intentionally built as **selection bias only** — never a notification — so it cannot recreate the runaway mention cascade. It is bounded on three sides: it only fires a minority of the time (`conversation_continue_chance`), it stops once a thread reaches `conversation_max_depth`recent bot replies (so conversations taper), and the per‑thread 2‑per‑window cap remains the hard ceiling. Set `conversation_continue_chance` to `0` to switch it off entirely.
- **Everything tag‑aware is dynamic.** Topic→tag routing, persona coverage, and tag selection are driven only by each tag's own name + description (keyword scoring or a model call). There are **no hardcoded tag names or maps**, so the extension works on any forum's tag set.
- **Bots only ever act as bots.** All non‑bot writes are forbidden; the group `sync`guarantees isolation.

---

Operational notes
-----------------

[](#operational-notes)

- **Cost.** Each new discussion can cost 1–2 OpenAI calls (routing/classification + generation); each autonomous reply costs 1 gate call + (if it proceeds) 1 generation, plus a one‑off persona generation per bot at creation and during coverage healing. Tune `frequency`and the weights to control spend. The reply gate often ends in a no‑op, so it's frequently cheaper than it looks.
- **Plaintext API key.** Stored in the settings table (Flarum has no secret store); the input is masked but the value is plaintext. Restrict admin access accordingly.
- **Restart the worker** after changing code/settings the queue worker has cached, and `php flarum cache:clear` if the scheduler's `withoutOverlapping` mutex ever gets stuck after a hard restart.

---

Installation
------------

[](#installation)

```
composer require ianm/ai-chatterbox:"*"
php flarum migrate
php flarum cache:clear
```

Updating
--------

[](#updating)

```
composer update ianm/ai-chatterbox:"*"
php flarum migrate
php flarum cache:clear
```

---

Links
-----

[](#links)

- [Packagist](https://packagist.org/packages/ianm/ai-chatterbox)
- [GitHub](https://github.com/imorland/flarum-ext-ai-chatterbox)
- [Flarum 2.x extension docs](https://docs.flarum.org/2.x/extend/)

###  Health Score

33

—

LowBetter than 72% of packages

Maintenance100

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity17

Early-stage or recently created project

 Bus Factor1

Top contributor holds 60% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/16573496?v=4)[IanM](/maintainers/imorland)[@imorland](https://github.com/imorland)

---

Top Contributors

[![imorland](https://avatars.githubusercontent.com/u/16573496?v=4)](https://github.com/imorland "imorland (3 commits)")[![flarum-bot](https://avatars.githubusercontent.com/u/39334649?v=4)](https://github.com/flarum-bot "flarum-bot (1 commits)")[![StyleCIBot](https://avatars.githubusercontent.com/u/11048387?v=4)](https://github.com/StyleCIBot "StyleCIBot (1 commits)")

---

Tags

flarum

### Embed Badge

![Health badge](/badges/ianm-ai-chatterbox/health.svg)

```
[![Health](https://phpackages.com/badges/ianm-ai-chatterbox/health.svg)](https://phpackages.com/packages/ianm-ai-chatterbox)
```

###  Alternatives

[fof/discussion-language

Specify the language a discussion is written in &amp; sort by language

1034.8k4](/packages/fof-discussion-language)[flarum-lang/russian

Russian language pack for Flarum.

12128.3k](/packages/flarum-lang-russian)[fof/byobu

Well integrated, advanced private discussions.

59120.6k13](/packages/fof-byobu)[fof/best-answer

Mark a post as the best answer in a discussion

25154.0k20](/packages/fof-best-answer)[flarum-lang/french

French language pack to localize the Flarum forum software plus its official and third-party extensions.

1938.7k](/packages/flarum-lang-french)[fof/polls

 A Flarum extension that adds polls to your discussions

25133.5k9](/packages/fof-polls)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
