tomatophp/filament-menus
TomatoPHP Filament Menus is a flexible menu builder for Laravel and Filament apps. Create and manage menus with nested items in the admin panel, assign them to custom locations, and render dynamic navigation anywhere using a simple API.
Check the version wall before anything else. v5.0.0 requires Filament v5, Laravel 12/13, and PHP 8.2+. If you're on Filament v3 or v4, stop here and decide whether you're upgrading Filament first — the package is coupled to a young Filament major, and there's no benefit in fighting two upgrades at once.
Install in four moves:
composer require tomatophp/filament-menus
PanelProvider using the snippet as shipped in the v5.0.0 README — both the plugin snippet and the FilamentMenuLoader namespace were corrected in that release, and older blog posts/forks will hand you a broken import.php artisan migrate.Realistic estimate: half a day for a Filament-fluent dev to a working menu; add another half-day if you're installing filament-shield at the same time.
Read three things before writing integration code, in this order:
FilamentMenuLoader) and the Blade/Livewire component (<x-filament-menu>). These are the only integration points most apps touch.First use case — replace one hardcoded panel nav. Create a menu keyed main, add a route link and an external URL, drag them into order, then point the panel at that menu instead of ->navigationItems([...]). That single swap exercises the resource, ordering, link resolution, and the panel loader in one pass. Then add a footer menu and render it in a Blade layout via <x-filament-menu> — that proves the frontend/Livewire path independently of the panel. If both work, the package fits. If either fights you, you've learned it cheaply.
Wrap the package in your own resolver on day one. Don't import package classes from layouts or PanelProvider. Create a small MenuResolver or view composer that takes a menu key, returns a tree, and is the sole import site for the package.
// app/Support/MenuResolver.php
public function for(string $key): Collection
{
return Cache::rememberForever("menu:{$key}", fn () =>
\TomatoPHP\FilamentMenus\Models\Menu::query()
->where('key', $key)
->firstOrFail()
->items // illustrative — read the source for the real relation
);
}
This costs an hour and buys a one-file swap when you fork, replace, or pin an unreleased fix. Given the adoption profile (35 stars, zero dependents, single maintainer), it's the highest-leverage habit here.
Panel navigation mode. Replace ->navigationItems() and ->navigationGroups() with the loader pointed at a menu key, in a single deliberate commit. Half-migrated nav is the primary integration failure mode — you get duplicated or shadowed items that are genuinely painful to debug. In the same pass, audit every shouldRegisterNavigation() call: those are code-level visibility decisions that now compete with DB-level ones.
Standalone render mode. For storefront headers, portal sidebars, or a second panel, use <x-filament-menu> with the menu key, kept in a layout partial — not scattered through views. One home for the markup contract means one place to re-check after upgrades.
Keyed menus as your addressing scheme. Name menus admin-main, client-{tenant}-sidebar, marketing-footer. Resolve the key from the current panel/tenant in your resolver and pass it to the renderer. This is where the package earns its keep — one schema driving several branded shells — but only if you never hardcode a menu ID in a Blade file.
Centralize link resolution. External URLs, route names, and model references resolve through different code paths. Put URL generation in your adapter and add form-level validation (or an observer) so a stale route name or a model reference to a deleted record fails loudly in staging rather than shipping a dead nav item. If you only do one integration nicety, do this one.
Permission-aware nav. With filament-shield installed, items the current user may not see are skipped at render time — a real win over hand-rolling visibility. Keep the boundary in your head: this is filtering over your existing policy layer, not a per-item ACL editor. If you run plain spatie/permission + policies or a bespoke RBAC table, don't assume it generalizes — test it explicitly and filter in your resolver if it doesn't.
Cache deliberately, invalidate on write. Navigation moved from compile-time to query-time, which means it can be stale and wrong after a restore or bad seed. Cache the resolved tree and forget it in an observer on the menu/menu-item models. Don't skip caching "because the query is small" — skip it because you measured it.
Test the regressions that already bit someone. Use the shipped Menu and MenuItem factories for fixtures instead of hand-inserting rows. Assert two things in feature tests: the panel nav contains the expected items for a given user, and the Blade component renders for a menu whose items have no badge and no translation in the current locale. Those were the exact v5 crash cases, and they're the ones you'll reintroduce by publishing views.
lara-zeus/spatie-translatable ^2.0, not upstream Spatie. If your app already ships spatie/laravel-translatable, check for duplicate service providers and constraint conflicts before treating this as drop-in. Make that supply-chain call consciously.filament-shield in the release notes. Verify against your actual authorization stack — a silent permission leak in nav is minor, but the same assumption applied elsewhere isn't.navigationItems() in place while enabling DB menus produces shadowed or duplicated items. Pick replace-or-augment deliberately.FilamentMenuLoader namespace. It changed in v5.0.0. After upgrading, grep for the old import — a stale reference surfaces as a confusing class-not-found, not a menu error.<x-filament-menu>, and the loader. Fixed in v5 — but your published copies can reintroduce the same crashes. Re-check after every upgrade.Menu/MenuItem factories rather than forking them in place and forgetting.How can I help you explore Laravel packages today?