A Filament plugin for building menus with drag-and-drop ordering, nesting, custom links, and dynamic panels.
- PHP 8.3+
- Filament 5.0+
- Laravel 12+
- Update your
composer.json:
composer require datlechin/filament-menu-builder:^1.0- Publish and run the new migration to add the
panel,icon, andclassescolumns:
php artisan vendor:publish --tag="filament-menu-builder-migrations"
php artisan migrateThe upgrade migration checks for existing columns before adding them, so it's safe on fresh installs too.
- Re-publish the config file if you published it previously:
php artisan vendor:publish --tag="filament-menu-builder-config" --forceInstall via Composer:
composer require datlechin/filament-menu-builderPublish and run the migrations:
php artisan vendor:publish --tag="filament-menu-builder-migrations"
php artisan migrateOptionally, publish the config file:
php artisan vendor:publish --tag="filament-menu-builder-config"Or use the install command:
php artisan filament-menu-builder:installRegister the plugin in your panel provider:
use Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentMenuBuilderPlugin::make(),
]);
}Locations define where menus appear in your application:
FilamentMenuBuilderPlugin::make()
->addLocations([
'header' => 'Header',
'footer' => 'Footer',
])Panels provide item sources for menus, either from Eloquent models or static lists.
Implement MenuPanelable on your model:
use Datlechin\FilamentMenuBuilder\Contracts\MenuPanelable;
class Page extends Model implements MenuPanelable
{
public function getMenuPanelTitle(): string
{
return $this->title;
}
public function getMenuPanelUrl(): string
{
return route('pages.show', $this);
}
public function getMenuPanelName(): string
{
return 'Pages';
}
}Then register it:
use Datlechin\FilamentMenuBuilder\MenuPanel\ModelMenuPanel;
FilamentMenuBuilderPlugin::make()
->addMenuPanels([
ModelMenuPanel::make()
->model(Page::class),
])use Datlechin\FilamentMenuBuilder\MenuPanel\StaticMenuPanel;
FilamentMenuBuilderPlugin::make()
->addMenuPanels([
StaticMenuPanel::make()
->name('pages')
->add('Home', '/')
->add('About', '/about')
->add('Contact', '/contact'),
])add() also accepts target, icon, and classes:
StaticMenuPanel::make()
->name('social')
->add('GitHub', 'https://github.com', target: '_blank', icon: 'heroicon-o-code-bracket')
->add('Twitter', 'https://twitter.com', target: '_blank', classes: 'text-blue-500')The custom link panel is shown by default. The custom text panel (for non-link items like headings) is opt-in:
FilamentMenuBuilderPlugin::make()
->showCustomLinkPanel(true)
->showCustomTextPanel(true)Add extra fields to the menu or menu item forms:
use Filament\Forms\Components\TextInput;
FilamentMenuBuilderPlugin::make()
->addMenuFields([
TextInput::make('description'),
])
->addMenuItemFields([
TextInput::make('badge'),
])Singular methods work too:
FilamentMenuBuilderPlugin::make()
->addMenuField(TextInput::make('description'))
->addMenuItemField(TextInput::make('badge'))Multiple calls are merged, so fields registered from different service providers won't overwrite each other.
FilamentMenuBuilderPlugin::make()
->navigationLabel('Menus')
->navigationGroup('Content')
->navigationIcon('heroicon-o-bars-3')
->navigationSort(3)
->navigationCountBadge(true)Nesting via indent/unindent actions is enabled by default:
FilamentMenuBuilderPlugin::make()
->enableIndentActions(true)Built-in multilingual support with no extra packages required. Translatable fields are stored as JSON with locale tabs in the form UI.
- Enable translatable with your locales:
FilamentMenuBuilderPlugin::make()
->translatable(['en', 'nl', 'vi'])- Publish and run the migration to convert columns from
stringtojson:
php artisan vendor:publish --tag="filament-menu-builder-translatable-migrations"
php artisan migrateExisting string data is wrapped in the default locale (en). Edit $defaultLocale in the published migration to change this.
Only MenuItem.title is translatable by default:
FilamentMenuBuilderPlugin::make()
->translatable(['en', 'nl', 'vi'])
->translatableMenuItemFields(['title']) // default
->translatableMenuFields(['name']) // opt-in: make Menu name translatable tooUse resolveLocale() in Blade to display titles in the current locale:
@foreach($menu->menuItems as $item)
<a href="{{ $item->url }}">
{{ $item->resolveLocale($item->title) }}
</a>
@endforeachresolveLocale() returns the translation for app()->getLocale(), falls back to the first available translation, or returns the raw string for non-translatable setups.
The JSON format is compatible with Spatie Laravel Translatable. If you add HasTranslations to a custom model, the plugin detects it and defers to Spatie's mutators.
use Spatie\Translatable\HasTranslations;
class CustomMenuItem extends MenuItem
{
use HasTranslations;
public array $translatable = ['title'];
}Replace the default models with your own:
FilamentMenuBuilderPlugin::make()
->usingMenuModel(CustomMenu::class)
->usingMenuItemModel(CustomMenuItem::class)
->usingMenuLocationModel(CustomMenuLocation::class)Retrieve a menu by location. Results are cached and automatically busted on changes:
use Datlechin\FilamentMenuBuilder\Models\Menu;
$menu = Menu::location('header');Render menu items:
@if($menu)
<nav>
<ul>
@foreach($menu->menuItems as $item)
<li class="{{ $item->classes }} {{ $item->isActive() ? 'active' : '' }}">
@if($item->url)
<a href="{{ $item->url }}" target="{{ $item->target }}" @if($item->rel) rel="{{ $item->rel }}" @endif>
{{ $item->resolveLocale($item->title) }}
</a>
@else
<span>{{ $item->resolveLocale($item->title) }}</span>
@endif
@if($item->children->isNotEmpty())
<ul>
@foreach($item->children as $child)
<li>
<a href="{{ $child->url }}">{{ $child->resolveLocale($child->title) }}</a>
</li>
@endforeach
</ul>
@endif
</li>
@endforeach
</ul>
</nav>
@endifCheck if a menu item matches the current URL:
$item->isActive(); // exact URL match
$item->isActiveOrHasActiveChild(); // matches self or any descendant| Property | Type | Description |
|---|---|---|
title |
string|array | The display title (array when translatable) |
url |
?string | The URL (null for text-only items) |
target |
string | Link target (_self, _blank, etc.) |
icon |
?string | Icon identifier (e.g. heroicon-o-home) |
classes |
?string | CSS classes for the item |
rel |
?string | Link rel attribute (e.g. nofollow noopener) |
type |
string | Panel name / source type (accessor) |
children |
Collection | Nested child items |
composer testSee CHANGELOG for recent changes.
MIT License. See LICENSE.md.
