Skip to content

datlechin/filament-menu-builder

Repository files navigation

Filament Menu Builder

Latest Version on Packagist GitHub Tests Action Status Total Downloads

Filament Menu Builder

A Filament plugin for building menus with drag-and-drop ordering, nesting, custom links, and dynamic panels.

Requirements

  • PHP 8.3+
  • Filament 5.0+
  • Laravel 12+

Upgrading

From v0.7.x (Filament v3) to v1.x (Filament v5)

  1. Update your composer.json:
composer require datlechin/filament-menu-builder:^1.0
  1. Publish and run the new migration to add the panel, icon, and classes columns:
php artisan vendor:publish --tag="filament-menu-builder-migrations"
php artisan migrate

The upgrade migration checks for existing columns before adding them, so it's safe on fresh installs too.

  1. Re-publish the config file if you published it previously:
php artisan vendor:publish --tag="filament-menu-builder-config" --force

Installation

Install via Composer:

composer require datlechin/filament-menu-builder

Publish and run the migrations:

php artisan vendor:publish --tag="filament-menu-builder-migrations"
php artisan migrate

Optionally, publish the config file:

php artisan vendor:publish --tag="filament-menu-builder-config"

Or use the install command:

php artisan filament-menu-builder:install

Usage

Register the plugin in your panel provider:

use Datlechin\FilamentMenuBuilder\FilamentMenuBuilderPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            FilamentMenuBuilderPlugin::make(),
        ]);
}

Locations

Locations define where menus appear in your application:

FilamentMenuBuilderPlugin::make()
    ->addLocations([
        'header' => 'Header',
        'footer' => 'Footer',
    ])

Menu Panels

Panels provide item sources for menus, either from Eloquent models or static lists.

Model Panel

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),
    ])

Static Panel

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')

Custom Link & Custom Text Panels

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)

Custom Fields

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.

Customizing Navigation

FilamentMenuBuilderPlugin::make()
    ->navigationLabel('Menus')
    ->navigationGroup('Content')
    ->navigationIcon('heroicon-o-bars-3')
    ->navigationSort(3)
    ->navigationCountBadge(true)

Indent / Unindent

Nesting via indent/unindent actions is enabled by default:

FilamentMenuBuilderPlugin::make()
    ->enableIndentActions(true)

Translatable Menus

Built-in multilingual support with no extra packages required. Translatable fields are stored as JSON with locale tabs in the form UI.

Setup

  1. Enable translatable with your locales:
FilamentMenuBuilderPlugin::make()
    ->translatable(['en', 'nl', 'vi'])
  1. Publish and run the migration to convert columns from string to json:
php artisan vendor:publish --tag="filament-menu-builder-translatable-migrations"
php artisan migrate

Existing string data is wrapped in the default locale (en). Edit $defaultLocale in the published migration to change this.

Configuring Translatable Fields

Only MenuItem.title is translatable by default:

FilamentMenuBuilderPlugin::make()
    ->translatable(['en', 'nl', 'vi'])
    ->translatableMenuItemFields(['title'])  // default
    ->translatableMenuFields(['name'])       // opt-in: make Menu name translatable too

Rendering Translated Titles

Use resolveLocale() in Blade to display titles in the current locale:

@foreach($menu->menuItems as $item)
    <a href="{{ $item->url }}">
        {{ $item->resolveLocale($item->title) }}
    </a>
@endforeach

resolveLocale() returns the translation for app()->getLocale(), falls back to the first available translation, or returns the raw string for non-translatable setups.

Spatie Translatable Compatibility

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'];
}

Custom Models

Replace the default models with your own:

FilamentMenuBuilderPlugin::make()
    ->usingMenuModel(CustomMenu::class)
    ->usingMenuItemModel(CustomMenuItem::class)
    ->usingMenuLocationModel(CustomMenuLocation::class)

Rendering Menus

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>
@endif

Active State Detection

Check if a menu item matches the current URL:

$item->isActive();                 // exact URL match
$item->isActiveOrHasActiveChild(); // matches self or any descendant

MenuItem Properties

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

Testing

composer test

Changelog

See CHANGELOG for recent changes.

License

MIT License. See LICENSE.md.

About

Create and manage menu in your Filament app.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors