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

Filament Menu Laravel Package

wotz/filament-menu

wotz/filament-menu is a Filament package for creating and managing navigation menus from your admin panel. Build menu structures, attach links, and render them in your Laravel application through a simple Filament-integrated workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and inspect before you wire anything up.

composer require wotzebra/filament-menu
php artisan vendor:publish --tag="filament-menu-migrations"
php artisan vendor:publish --tag="filament-menu-config"
php artisan migrate

Publishing tags for an unproven package are worth guessing at first — if the tag names differ, run php artisan vendor:publish interactively and pick the provider by number. That single step tells you more than the README: it reveals whether config and migrations are separable, and whether views are publishable (which is your escape hatch for custom markup later).

Where to look first, in this order:

  1. database/migrations/*_create_*menu*_tables.php — the schema is the real API. Note whether items are a self-referencing tree (parent_id) or a flat sortable list, whether there's a linkable_type/linkable_id polymorphic pair, and whether a panel/tenant_id column exists. Every integration decision downstream follows from these three facts.
  2. config/filament-menu.php — look for cache TTL, model class overrides, and a list of "linkable" models. Model overrides mean you can subclass instead of forking.
  3. src/*Plugin.php and src/Resources/ — confirm the plugin class FQCN and whether the resource is registered automatically or must be added to the panel's resources().
  4. resources/views/ — only if you intend to control markup; otherwise render via the package's component.

Register the plugin in your panel provider:

->plugin(\Wotz\FilamentMenu\FilamentMenuPlugin::make())

(Confirm the namespace from composer.json's PSR-4 block — with zero dependents there's no blog post to copy from.)

First use case: create a "Main Navigation" menu in the admin, add three items (one linking to a Page model, one to a route, one external), then render it in layouts/app.blade.php via the package's Blade component or a Menu::findBySlug('main')-style helper. Getting one item of each link type working end-to-end surfaces 90% of the gotchas immediately.

Implementation Patterns

Treat menus as content, not config. The whole value of the package is letting an editor change nav without a deploy. That means:

  • Seed the container, not the contents. In a seeder, Menu::updateOrCreate(['slug' => 'main'], ['name' => 'Main Navigation']) so fresh environments have a menu to attach items to, but let humans own the items. Never seed items in production seeders — you'll fight duplicates on every deploy.
  • Polymorphic links over hardcoded URLs. If the schema has linkable_type/linkable_id, wire your Page, Post, Category models into the package's linkable config. Editors then pick a page by title, and the URL is resolved at render time — which survives slug changes and domain moves. Fall back to a raw url column only for external links.
  • Cache the rendered HTML, not the query. Recursive tree queries are cheap; Blade recursion in a loop is not. Wrap the render in Cache::remember("menu.main.{$locale}", 3600, fn () => view(...)->render()). Key by slug and locale and tenant if you have one.
  • Invalidate on write, explicitly. Register an observer on the package's MenuItem model (or listen to saved/deleted) and flush by cache tag. Filament's drag-to-reorder often writes order columns in bulk without firing model events — verify this, and if so, hook the resource's reorder action or bust the cache on every menu page render.

Rendering partial: keep it in your app, not the vendor views.

@foreach ($items->whereNull('parent_id') as $item)
  <a href="{{ $item->url }}" target="{{ $item->target ?? '_self' }}"
     @class(['active' => $item->isActive()])>{{ $item->label }}</a>
  @if ($item->children->isNotEmpty())
    @include('partials.menu-items', ['items' => $item->children])
  @endif
@endforeach

Publish and copy the vendor view into your app rather than @include-ing filament-menu::... — you want the markup under your control and diffable when the package updates.

Multi-panel / multi-tenant: register the plugin only in the panels that need menu editing (usually one admin panel), and if the schema carries a tenant or panel column, scope the resource query in a getEloquentQuery() override on a subclassed resource. Don't let editors see another tenant's menus.

Authorization: add a MenuPolicy and register it. Menu editing is a content-editor permission, not a super-admin one — but deleting a menu that a live layout depends on should be restricted harder than editing items.

Testing: assert on a rendered string, not on models. $this->get('/')->assertSee('Main Navigation') plus a test that a hidden menu item 404s its page catches most regressions.

Gotchas and Tips

  • Zero stars, zero dependents means you are the QA team. Pin the exact version in composer.json ("wotzebra/filament-menu": "1.2.3", not ^1.0), commit composer.lock, and read src/ before shipping. Budget for the possibility of forking — check the license (MIT, so forking is fine) and keep your overrides in subclasses and app-level views so a fork stays cheap.
  • Filament major-version drift is the highest-probability breakage. Confirm which Filament version the package targets (composer.json require block) before anything else. Filament v3 → v4 changed form/table APIs substantially; a plugin that hasn't been touched will throw on ->reorderable() or schema method names.
  • Reorder columns in bulk operations usually skip model events. If your cache doesn't bust when an editor drags items, this is why. Either disable caching on the menu resource pages or override the reorder action to flush explicitly. Test by reordering, then loading the front end in the same request cycle.
  • Storing absolute URLs in the DB is a trap. They break on domain change, on locale-prefixed routes (/en/about vs /about), and on staging→production promotion. Prefer linkable models or route names with parameters; keep absolute URLs only for genuinely external links.
  • N+1 on nested trees. Eager load the recursive children relation (with('childrenRecursive') or whatever the package names it). A 40-item menu with lazy children will issue 40 queries per page load, and it will never show up in local profiling because your local DB is warm.
  • Deleted-linkable orphans. When a linked Page is deleted, the menu item survives pointing at nothing. Add a nullOnDelete FK if the schema allows, or a Menu::prune() command scheduled nightly, plus a isActive()/url() null-guard so the render doesn't 500 the whole layout.
  • Debugging registration: php artisan about shows registered plugins and resources per panel. If the Menus resource doesn't appear, it's almost always a missing ->plugin() call, a cached config (php artisan optimize:clear), or a policy silently denying viewAny.
  • Extension points to prefer, in order: config-driven model overrides → subclassed resource registered in place of the vendor one → published views → fork. Never edit vendor/.
  • Watch the release cadence. The last release is recent but the package has no downstream users, so regressions ship unnoticed. Subscribe to the repo's releases and run composer outdated in CI; treat a patch bump as a code review, not an automatic merge.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
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