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 Builder Laravel Package

datlechin/filament-menu-builder

A flexible menu builder for Filament that lets you create and manage nested navigation menus with drag-and-drop sorting, custom links, and model-linked items. Easily build multi-level menus and render them anywhere in your Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites — Filament v3 (v4 support landed in later releases; check the filament/filament constraint in composer.json before upgrading panels), PHP 8.1+, and a database. The package ships its own menus / menu_items tables, so treat it as persistent app data, not a dev-only tool.

Install

composer require datlechin/filament-menu-builder
php artisan vendor:publish --tag="filament-menu-builder-migrations"
php artisan migrate
php artisan vendor:publish --tag="filament-menu-builder-config"   # optional but recommended

Register the plugin in the panel provider (app/Providers/Filament/AdminPanelProvider.php):

use Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugin(FilamentMenuBuilderPlugin::make());
}

Reload the admin, and a Menus entry appears in the navigation. Create a menu (e.g. "Main"), give it a slug (main), then add items: custom links, routes, or whatever link types are registered. Items are reorderable by drag-and-drop and nestable via the parent selector.

Where to look first

  1. config/filament-menu-builder.php — model bindings and any link-type registration. Read this before writing models.
  2. src/Models/Menu.php and src/Models/MenuItem.php — the link/type/target/order/parent_id columns and the URL accessor are what you render against.
  3. src/FilamentMenuBuilderPlugin.php — the fluent ->make() chain is the extension surface (models, resources, link types).
  4. The menu_items migration — confirm FK/cascade behaviour before you wire up deletion flows.

First use case: build a main menu in the admin, then render it in resources/views/layouts/app.blade.php with a recursive Blade partial, and cache the resulting tree. Don't hard-code navigation arrays anymore — the whole point is that a non-developer can edit the nav.

Implementation Patterns

Look up menus by slug, never by ID. IDs differ between local, staging and production, so seed a main and footer menu in a seeder and reference them by slug everywhere in code.

// database/seeders/MenuSeeder.php
Menu::firstOrCreate(['slug' => 'main'], ['name' => 'Main navigation']);
Menu::firstOrCreate(['slug' => 'footer'], ['name' => 'Footer links']);

Render with a recursive partial, not a package-specific Blade tag — you keep full control over markup and Tailwind classes:

{{-- resources/views/partials/menu.blade.php --}}
@props(['items'])
<ul {{ $attributes->merge(['class' => 'flex gap-6']) }}>
    @foreach ($items->sortBy('order') as $item)
        <li @class(['font-semibold' => $item->children->isNotEmpty()])>
            <a href="{{ $item->url }}" target="{{ $item->target ?? '_self' }}"
               @if($item->target === '_blank') rel="noopener" @endif>
                {{ $item->name }}
            </a>
            @if($item->children->isNotEmpty())
                @include('partials.menu', ['items' => $item->children])
            @endif
        </li>
    @endforeach
</ul>

Cache the tree and invalidate on write. Menus are read on every page and written a few times a year — the ideal cache candidate. An observer on both models is the cleanest invalidation point:

// app/Observers/MenuCacheObserver.php
public function saved(Model $model): void
{
    Cache::forget('menu.main');
    Cache::forget('menu.footer');
}

public function deleted(Model $model): void
{
    $this->saved($model);
}

Resolve menus through a service, not the view. A small MenuRepository (with Cache::rememberForever) injected into a view composer keeps Blade clean and gives you one place to change the query:

View::composer(['layouts.app', 'layouts.marketing'], function ($view) {
    $view->with([
        'mainMenu'   => $this->menus->get('main'),
        'footerMenu' => $this->menus->get('footer'),
    ]);
});

Multi-location / multi-tenant sites. If each tenant needs its own nav, add a tenant_id (or morph) column to menus via your own migration, swap the model in config, and add a global scope. Slugs alone won't be unique across tenants — make the unique index (tenant_id, slug).

Custom link types are the real integration point. The plugin's value is that "link to a Route" or "link to a Page model" can be a first-class admin UI option. Register project-specific types through the plugin's fluent API so editors can pick a Page record and have the URL resolved at render time — that keeps URLs correct when slugs change, which is the main failure mode of hand-typed custom links.

Wire it into Filament navigation tests. Use Livewire/Filament test helpers to assert the resource pages boot without a 500 — plugin registration errors surface here first:

it('renders the menu resource', function () {
    $this->actingAs(User::factory()->create())
        ->get(MenuResource::getUrl())
        ->assertOk();
});

Gotchas and Tips

  • Publish before you migrate. Running php artisan migrate before the vendor publish produces a missing-table error on the first admin visit, which looks like a plugin bug but is just an un-run install step.
  • Config caching breaks plugin changes. After editing the config or swapping model bindings, run php artisan config:clear (and optimize:clear) — stale cached config is the #1 source of "my custom model is ignored."
  • Slug renames silently break the site. The admin lets editors rename a menu's slug; your Blade code looks up main. Consider making the slug read-only in the resource form, or add a key column that your code uses instead.
  • Eager-load the whole tree. with('items') only gets depth 1; children load lazily and you'll get N+1 queries on a nested nav. Use with('items.children.children') to a known depth and cache the result — do not load the tree uncached on every request.
  • Always orderBy('order'). The drag-and-drop UI writes an order column; relying on the default id ordering makes the rendered menu look "random" after a reorder.
  • Cascade deletes. Check the menu_items FK: deleting a menu should delete its items. If the migration doesn't cascade (or your DB driver ignores FKs), you'll leave orphaned items that reappear if a menu with the same ID range is created.
  • Escape names. If the name field is a rich/HTML editor in your build, {!! !!} in a nav is an XSS vector. Prefer {{ $item->name }} and store plain text; use a separate description field if you need markup.
  • target="_blank" without rel="noopener" is a live security finding in audits. Add it in the partial, not the admin — editors won't reliably set rel.
  • Authorization. The resource ships with standard Filament policies; in a multi-role app, add a MenuPolicy early. Otherwise every logged-in user who can reach the panel can rewrite the site navigation.
  • Extension points to know: the plugin's fluent methods for swapping the Menu/MenuItem models, registering custom link types, and toggling the resource registration (useful when you want menus managed but rendered by your own UI only).
  • Upgrading Filament majors: this package lags panel releases. Pin filament/filament in composer.json and upgrade the plugin in the same PR — a Filament v4 panel with a v3-only plugin version typically fails at plugin boot with a class/method signature error, not a helpful message.
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