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

Framework Laravel Package

flarum/framework

Flarum is a free, open-source forum platform built on a Laravel-style PHP backend with a modern JavaScript frontend. Its fast, extensible architecture offers a clean REST API and a rich extension system, making it easy to launch and customize a beautiful community forum.

View on GitHub
Deep Wiki
Context7

Getting Started

Flarum is not a Laravel application, but it is Laravel-flavoured: it pulls in illuminate/database, illuminate/validation, illuminate/events and a container that wraps Laravel's, and its extensions are Composer packages with migrations and service providers. If you know Laravel, the mental model transfers for ~60% of the backend and almost none of the frontend (Mithril.js SPA, no Blade, no Vue/React).

Minimal setup

composer create-project flarum/flarum forum
cd forum
# edit .env: FLARUM_URL, DB_* credentials (MySQL/MariaDB; SQLite works for local dev)
php flarum install            # or: php flarum migrate, then visit / to run the installer
php flarum serve              # local dev on :8000; in prod point nginx at public/index.php

Then log in at /admin. The php flarum binary is your artisan: list, info, migrate, cache:clear, assets:publish, extension:enable vendor/name.

Where to look first

  1. vendor/flarum/core/src/ — read Extend/ (the extender classes) before anything else. Extenders are the public API of the framework.
  2. vendor/flarum/core/src/Api/Controller/ and Api/Serializer/ — every endpoint is one controller + one serializer, easy to copy.
  3. vendor/flarum/core/migrations/ — the schema you'll be extending.
  4. extend.php at the site root — the lightest place to prototype a listener or route before committing to a package.

First use case worth doing: a small extension that adds a custom API endpoint and a custom Post attribute, then surfaces it in the forum UI. It exercises routes, models, serializers, settings, permissions and the JS extension surface in one pass — after that, 90% of real work is variations on it.

Implementation Patterns

Extension anatomy

acme/reports/
├── composer.json        # "extra": { "flarum-extension": { "title": "...", "category": "feature" } }
├── extend.php           # returns an array of extender objects
├── migrations/          # auto-discovered, Laravel-style migrations
├── locale/en.yml
├── src/                 # PSR-4, namespaced Acme\Reports\
└── js/src/forum/index.ts, js/src/admin/index.ts, js/package.json

extend.php is a declarative manifest — resist the urge to write a fat service provider:

use Flarum\Extend;
use Flarum\Post\Event\Posted;

return [
    (new Extend\Routes('api'))
        ->get('/acme/reports', 'acme.reports.index', Controllers\ReportIndexController::class),

    (new Extend\Event())
        ->listen(Posted::class, Listeners\ScorePost::class),

    (new Extend\Model(Post::class))
        ->cast('acme_score', 'integer'),

    (new Extend\Settings())
        ->default('acme.reports.threshold', 3)
        ->serializeToForum('acmeThreshold', 'acme.reports.threshold', 'intval'),

    (new Extend\Locales(__DIR__.'/locale')),
];

API: controller + serializer + policy

The pattern is rigid, which is a feature — copy an existing pair and change the model:

class ReportIndexController extends AbstractListController
{
    protected $serializer = ReportSerializer::class;
    protected $limit = 20;

    protected function data(ServerRequestInterface $request, Document $document)
    {
        return Report::query()->where('status', 'open')->paginate(20);
    }
}

The serializer must explicitly expose every attribute/relationship; anything you forget is silently absent from the response. Authorisation goes through Flarum\User\AssertPermissionTrait / Gate with a Policy class registered via (new Extend\Policy())->modelPolicy(Report::class, ReportPolicy::class), mirrored into the admin UI with (new Extend\Frontend('admin')) + app.extensionData.for('acme-reports').registerPermission(...).

Migrations and models

Standard Illuminate\Database\Schema builders, wrapped:

return [
    'up' => fn (Builder $schema) => $schema->create('acme_reports', function (Blueprint $t) {
        $t->increments('id');
        $t->unsignedInteger('post_id')->index();
        $t->string('status')->default('open');
        $t->timestamps();
    }),
    'down' => fn (Builder $schema) => $schema->drop('acme_reports'),
];

Models extend Flarum\Database\AbstractModel — Eloquent relationships, casts, scopes and $dates all work. Cross-extension relationships should be registered through (new Extend\Model(Post::class))->belongsTo(...) rather than edited into core.

Frontend extension

Mithril + extend()/override() against the class registry:

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

extend(IndexPage.prototype, 'sidebarItems', function (items) {
  items.add('acme', <AcmeWidget threshold={app.forum.attribute('acmeThreshold')} />, 5);
});

extend = wrap a method and mutate its output (default choice); override = replace it (fragile, breaks on upgrade). Read settings from app.forum.attribute() / app.data.settings, never hardcode.

Integration with your Laravel app

Do not share the database. Authenticate over the API with Authorization: Token <key> (keys generated in Admin → API Keys) and talk JSON:API. For SSO, add an OAuth/OIDC extension and keep Flarum as the identity consumer; for outbound events, listen to domain events (Posted, UserRegistered) and dispatch to your Laravel app via HTTP or a shared Redis/queue bridge. Anything long-running should be a php flarum console command ((new Extend\Console())->command(...)) driven by cron — Flarum has no built-in queue.

Testing

Use flarum/testing's integration TestCase; it boots the real app with only the extensions you enable, which is the fastest way to catch extenders that break on upgrade:

protected function setUp(): void
{
    parent::setUp();
    $this->extend('acme/reports');
    $this->prepareDatabase(['acme_reports' => []]);
}

Gotchas and Tips

  • Unlearn Artisan assumptions. No Blade, no routes/web.php, no make:* generators. Routing is FastRoute, middleware is PSR-15, views are Mithril. Don't reach for view() — it doesn't exist.
  • Extender changes are cached. After editing extend.php, run php flarum cache:clear or you will debug a ghost for twenty minutes.
  • public/assets must be writable. Assets are compiled at runtime; in production always php flarum assets:publish as part of deploy, after composer install --no-dev and php flarum migrate.
  • Serializer omissions look like bugs. A field missing from the API response is almost always a missing getDefaultAttributes() entry or a missing ->fields() whitelist — not a database problem.
  • extend.php is authoritative, service providers are escape hatches. If you find yourself registering routes/events manually inside a provider, you're fighting the framework and will break on minor upgrades.
  • Extension ID ≠ package name. extra.flarum-extension and the Composer name drift apart; php flarum info listing an extension as disabled after a failed boot usually means a fatal in an extender or a missing migration.
  • Logs are in storage/logs/, not storage/logs/laravel.log. Turn up verbosity with FLARUM_DEBUG=true in .env (or 'debug' => true in root config.php) — the default production error page is deliberately opaque.
  • Version pinning is the whole upgrade story. Flarum 2.x rewrote the frontend in TypeScript and moved the compat layer; extensions declaring flarum/core: ^1.0 silently won't enable. Constrain tightly and test with the integration suite before bumping.
  • Permissions are enforced at the API, not the UI. Hiding a button in Mithril grants nothing; if an endpoint lacks a policy or assertCan, the data is public. Audit every controller you add.
  • Prefer extenders and listeners over override(). Overrides are the single largest source of "works until 2.x" breakage in the ecosystem; wrap and mutate instead.
  • No queue, no scheduler out of the box. Email sending, webhook fan-out and thumbnail work all happen inside the request — for anything slow, add a queue extension or cron + a custom command, and keep the request path thin.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo