flarum/core
Flarum is free, open-source forum software built on PHP with a fast, modern frontend. It's lightweight, extensible, and designed for smooth community discussions, offering a clean default theme and a rich extension ecosystem.
Flarum is not a Laravel application — it is a PHP forum platform built on selected Laravel components (illuminate/database, illuminate/validation, illuminate/support, service container, events). Treat it as a platform you extend, not an app whose framework you control.
Minimal setup:
composer create-project flarum/flarum forum
cd forum
php flarum install
For extension work, create a package with "type": "flarum-extension" and an extend.php file. The first places to read:
extend.php — registration point for all extenders.vendor/flarum/core/src/Extend/ — the supported extension surface.vendor/flarum/core/src/Api/ — JSON:API controllers, serializers, middleware.vendor/flarum/core/src/User/ — users, groups, policies, permissions.config.php and storage/logs/ — configuration and debugging.Your first useful use case: expose a small custom API endpoint and surface it in the forum UI. Start with a route and a controller, then add a frontend extender once the backend works.
Useful CLI commands:
php flarum list
php flarum migrate
php flarum cache:clear
php flarum assets:publish
php flarum extension:enable acme/stats
php flarum cache:clear is your closest equivalent to php artisan optimize:clear; run it after almost every backend change.
Extension anatomy. A typical extension has:
// extend.php
use Flarum\Extend;
return [
(new Extend\Routes('api'))
->get('/acme/stats', 'acme.stats', Acme\Stats\Controller\StatsController::class),
(new Extend\Frontend('forum'))
->js(__DIR__.'/js/dist/forum.js')
->css(__DIR__.'/less/forum.less'),
new Extend\Locales(__DIR__.'/locale'),
];
Register service providers with (new Extend\ServiceProvider())->register(...) / ->boot(...) when you need container bindings.
API endpoints. Prefer Flarum’s JSON:API controllers and serializers over returning arbitrary arrays. A list endpoint typically extends Flarum\Api\Controller\AbstractListController, sets public $serializer, and implements data(). For custom, non-model payloads you can implement Psr\Http\Server\RequestHandlerInterface directly, but clients will lose JSON:API conventions such as data, relationships, and included.
Extending existing API output. This is often more valuable than new endpoints:
use Flarum\Api\Serializer\ForumSerializer;
(new Extend\ApiSerializer(ForumSerializer::class))
->attributes(function ($serializer, $model, array $attributes) {
$attributes['acmeStats'] = [
'online' => 42,
];
return $attributes;
});
Use (new Extend\ApiController(SomeController::class))->prepareDataForSerialization(...) to eager-load relations and avoid N+1 queries in serializers.
Models and migrations. Use Flarum\Database\AbstractModel and normal Eloquent relationships. Flarum migrations are array-style, not Laravel migration classes:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->create('acme_stats', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->timestamps();
});
},
'down' => function (Builder $schema) {
$schema->dropIfExists('acme_stats');
},
];
Use the schema builder so the configured table prefix (flarum_ by default) is respected.
Events. Flarum dispatches domain events such as Flarum\Post\Event\Posted and Flarum\User\Event\Saving. Register listeners with:
(new Extend\Event())
->listen(\Flarum\Post\Event\Posted::class, Acme\Listeners\NotifyOnPost::class);
Settings. Inject Flarum\Settings\SettingsRepositoryInterface or resolve it from the container:
$settings = app(\Flarum\Settings\SettingsRepositoryInterface::class);
$settings->set('acme.stats.enabled', true);
$enabled = (bool) $settings->get('acme.stats.enabled');
Expose settings in the admin UI from the extension’s admin JS:
app.extensionData
.for('acme-stats')
.registerSetting({
setting: 'acme.stats.enabled',
type: 'boolean',
label: 'Enable stats',
});
Permissions. Define policies and register them:
(new Extend\Policy())
->modelPolicy(Acme\Stats::class, Acme\Policy\StatsPolicy::class);
Always enforce permissions server-side with $actor->assertCan(...) or policy checks inside controllers. Never rely on the frontend hiding a button.
Frontend extension. Flarum uses Mithril.js, not Blade/Vue/React. Extend existing components with extend():
import { extend } from 'flarum/common/extend';
import IndexPage from 'flarum/forum/components/IndexPage';
extend(IndexPage.prototype, 'hero', function (items) {
items.add('acme-stats', <div className="AcmeStats">...</div>);
});
Register custom frontend routes in JS via app.routes['acme.stats'] = { path: '/stats', component: StatsPage };.
Calling Flarum from a Laravel app. Do not merge codebases or share Eloquent models directly. Run Flarum as a separate service and integrate over its JSON:API:
curl -X POST https://forum.example.com/api/token \
-H 'Content-Type: application/json' \
-d '{"identification":"admin","password":"secret"}'
Then send Authorization: Token <token> and Accept: application/vnd.api+json on subsequent requests. Use pagination via page[number] and page[limit], and relationships via include.
Testing. Use flarum/testing integration tests. They boot a real Flarum app, run migrations, and let you act as a user:
use Flarum\Testing\integration\TestCase;
class StatsTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$this->extension('acme/stats');
}
public function test_endpoint_requires_auth(): void
{
$response = $this->send(
$this->request('GET', '/api/acme/stats')
);
$this->assertEquals(401, $response->getStatusCode());
}
}
php artisan, no Blade, no routes/web.php, and no full HTTP kernel. You get Laravel components, not the framework. Do not expect Laravel helpers, facades, or queue workers to behave like a standard Laravel app.config.php, not .env. Database, URL, and debug settings live in config.php. Set 'debug' => true locally and check storage/logs/flarum-*.log.composer require acme/stats only downloads code. Enable it through the admin panel or php flarum extension:enable acme/stats. If classes are missing after adding a package, run composer dump-autoload and php flarum cache:clear.extend.php, listeners, routes, or service providers, run php flarum cache:clear. Many “my extender isn’t running” bugs are cached container/extender state.js/dist/*.js files are what Flarum serves. Run npm run build in the extension and php flarum assets:publish after installing or updating extensions. If UI changes do not appear, verify the built assets and clear the cache.class ... extends Migration files into a Flarum extension. Run php flarum migrate after enabling the extension. Never edit core migrations.data/included. If you return raw arrays from a JSON:API endpoint, standard clients will break.ApiController extenders or controller data() methods, especially for list endpoints.assertCan. Guest actors are real actors; test unauthenticated and unauthorized cases explicitly.vendor/flarum/core. Use extenders. If you need a core change, fork and pin the package, because the next composer update will overwrite edits.flarum/core requirements. Use composer why-not flarum/core when an extension will not install, and pin core versions in production.php flarum migrate, php flarum cache:clear, and php flarum assets:publish. Skipping the asset publish step is a common cause of broken admin/forum UI after upgrades.new Extend\Locales(__DIR__.'/locale') and prefix keys with your extension ID to avoid collisions.How can I help you explore Laravel packages today?