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

Flarum Horizon Laravel Package

bokt/flarum-horizon

A Flarum extension that brings Laravel Horizon's queue monitoring dashboard into the Flarum admin panel, letting admins inspect Redis-backed workers, job throughput, wait times, and failed jobs. Requires Horizon and a compatible Flarum/PHP setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Flarum extension (blomstra/flarum-ext-horizon) that embeds Laravel Horizon's dashboard and supervisor runtime into a Flarum forum, not a standalone Laravel package. If you're coming from Laravel, the mental model carries over almost 1:1 — same Horizon code, same metrics/queue/tag views — but the entry point is the Flarum admin panel rather than /horizon.

  1. Install and enable:
composer require blomstra/flarum-ext-horizon
php flarum migrate
php flarum cache:clear
  1. Enable the extension in Admin → Extensions, then grant the horizon.view permission (via the admin permission grid or a group) to whoever should see the dashboard. The dashboard appears under the admin sidebar as Horizon.
  2. Confirm your queue is actually on Redis — Horizon only works with Redis queues. In config.php (or your env-driven config) the cache/queue/session drivers should point at Redis:
'cache' => ['driver' => 'redis'],
'session' => ['driver' => 'redis'],
'queue' => ['driver' => 'redis'],
  1. Start the supervisor process in your environment:
php flarum horizon
  1. First use case: open Horizon → Dashboard and verify Wait/Processes are non-zero for your default queue, then open Metrics and confirm jobs are being recorded. If the process count is 0, nothing downstream will work — fix that before touching anything else.

Where to look first when something's off: Dashboard → Supervisors (is the process alive?), Metrics → Jobs past hour (is work being processed?), then Failed Jobs.

Implementation Patterns

Treat php flarum horizon as a long-running daemon, not a command. In production run it under systemd or supervisor with auto-restart; Horizon is designed to be a single supervised process that forks the actual workers based on your configured maxProcesses. Running it from a shell session will silently die on logout.

[program:flarum-horizon]
command=php /var/www/forum/flarum horizon
directory=/var/www/forum
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/flarum-horizon.log
stopwaitsecs=3600

stopwaitsecs large is important — Horizon waits for in-flight jobs on shutdown, and a low value causes truncated jobs.

Configure supervisors per queue, not one global pool. The typical pattern is a short high-priority queue and a long low-priority one:

'defaults' => ['maxProcesses' => 1],
'environments' => [
    'production' => [
        'supervisor-critical' => [
            'connection' => 'redis',
            'queue'      => ['critical'],
            'balance'    => 'auto',
            'processes'  => 4,
            'tries'      => 3,
        ],
        'supervisor-bulk' => [
            'connection' => 'redis',
            'queue'      => ['default', 'emails'],
            'balance'    => 'auto',
            'maxProcesses' => 10,
            'memory'     => 256,
            'timeout'    => 300,
        ],
    ],
],

Then dispatch with intent: ->onQueue('critical') for anything user-facing, default for the rest.

Tags and job-level observability. Add tags() to jobs so the Horizon Monitoring tab is useful:

public function tags(): array
{
    return ['discussion:'.$this->discussion->id, 'user:'.$this->actor->id];
}

Monitor specific tags (e.g. discussion:*) with a Wait threshold and a notification route, so a stalled queue pages you before users notice.

Notifications are the operationally useful part. Wire Route::mail-style notification routes into config/horizon.php-equivalent config for long-wait and queue-length thresholds. Since this extension owns the config, verify which keys it exposes before relying on them — if a key isn't surfaced, use the dashboard's alerting on metrics instead.

Deploy pattern. Because Horizon holds config in memory, any config change requires a terminate:

php flarum cache:clear
php flarum horizon:terminate

Hook horizon:terminate into your deploy script (post-deploy, after cache clear). The supervisor restarts workers with new code; without it, old workers keep serving stale class definitions and you get "class not found" for newly added jobs.

Failed-job triage. Retry from the Horizon UI for one-offs; for systemic failures use the CLI so it's scriptable:

php flarum horizon:list-failed
php flarum horizon:retry 3a1f...

If the extension doesn't ship these, run them via Flarum's console with the package's service provider active.

Gotchas and Tips

Redis is doing double duty — separate the databases. Horizon stores metrics, job payloads and supervisor state in Redis, and Flarum's cache/sessions usually live there too. If you share a database and Redis hits maxmemory, evictions can drop Horizon keys mid-flight, producing phantom job loss with no failed jobs to show for it. Use a dedicated DB index (e.g. 'database' => 1 for cache, 2 for queues, 3 for Horizon) or a separate Redis instance.

Watch the eviction policy — 0.4.3 makes this visible. The new Redis admin widget surfaces maxmemory-policy. allkeys-lru and allkeys-random are dangerous here: Redis will happily evict queue and metrics keys under pressure, and jobs vanish without ever failing. Prefer noeviction for the queue DB (so writes fail loudly and you notice) or volatile-lru with TTLs. Before 0.4.3 you had to check this out-of-band with redis-cli config get maxmemory-policy; now it's on the dashboard — make it a standing item on your deploy checklist.

maxmemory display bug fixed in 0.4.3. Previously the dashboard always reported the memory limit as auto, even when you had configured an explicit memory value per supervisor. If you were tuning a memory-leaking worker and the UI kept saying auto, that was the bug — after upgrading, re-check the supervisor screens to confirm your actual configured limits are being applied. Any earlier conclusion you drew from that column is untrustworthy.

Missing PHP extensions fail confusingly. Horizon needs pcntl and posix. Without them the process starts, prints an error about pcntl_fork in the log, and dies — while the dashboard just shows zero supervisors. Check php -m | grep -E 'pcntl|posix' before debugging anything else.

memory per supervisor is a worker kill threshold, not a limit. When a worker exceeds it, Horizon gracefully exits it and the master respawns. Set it well below your memory_limit or you'll get hard fatals mid-job instead of clean restarts.

maxProcesses interacts with connection limits. Each worker holds a Redis connection. maxProcesses: 50 on a maxmemory-constrained Redis, or a managed Redis with a low maxclients, causes connection-refused storms that look like Redis outages. Keep maxProcesses times (workers + supervisors) comfortably under maxclients.

Long jobs need timeout < retry_after. If retry_after (queue config) is shorter than a job's runtime, Redis re-queues it while it's still running — double execution. Standard fix: timeout 300, retry_after 330, with a margin.

Config lives with the extension, so not every Horizon knob is exposed. This is the main divergence from vanilla Laravel. If you need a Horizon option the extension doesn't surface (e.g. trim, custom metrics), check the extension's source and its config layer before assuming it works — and prefer extension points over patching vendor/, which you'll lose on composer update.

Horizon in local dev is usually not what you want. QUEUE_CONNECTION=sync gives immediate feedback; running Horizon locally means background jobs execute out of band and are harder to debug with a debugger attached. Run it locally only when verifying supervisor balance or queue priority behaviour.

Don't run multiple Horizon masters on one Redis. Each master believes it owns the queues; you get duplicated dispatch and confusing metrics. One supervisor per environment, scaling via maxProcesses/balance, not by starting the command twice. Exception: deliberately split queues across separate masters with distinct --queue flags on separate Redis DBs.

Extension points. The UI is Vue-based on top of Horizon's API — for deeper integration (custom links from Flarum admin, embedding queue stats in a widget) hook into the extension's frontend extenders rather than overriding routes. On the backend, bind against Horizon's contracts (Laravel\Horizon\Contracts\JobRepository, MetricsRepository) in a service provider instead of reading Redis keys directly; those key formats are internal and have changed between Horizon majors.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager