Weave Code
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

Laravel Menu Laravel Package

spatie/laravel-menu

Build HTML menus in Laravel with a fluent API. Generate links from routes/actions/URLs, group items, add attributes/classes, and automatically set the active item from the current request. Extensible via macros; renders to HTML ready for Blade.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-menu
    

    Publish the config (optional):

    php artisan vendor:publish --provider="Spatie\Menu\MenuServiceProvider"
    
  2. First Menu Definition: Define a macro in a service provider (e.g., AppServiceProvider):

    use Spatie\Menu\Menu;
    use Spatie\Menu\MenuItem;
    
    Menu::macro('main', function () {
        return Menu::new()
            ->add('Home', 'home')
            ->add('About', 'about')
            ->add('Contact', 'contact')
            ->setActiveFromRequest();
    });
    
  3. Blade Rendering:

    <nav>
        {!! Menu::main() !!}
    </nav>
    

First Use Case: Basic Navigation

Create a simple top-level menu with active state detection:

Menu::macro('primary', function () {
    return Menu::new()
        ->action('HomeController@index', 'Home')
        ->action('ProductsController@index', 'Products')
        ->action('BlogController@index', 'Blog')
        ->setActiveFromRequest();
});

Implementation Patterns

Core Workflows

1. Macro-Based Menu Definitions

  • Best Practice: Centralize menu definitions in a dedicated service provider (e.g., MenuServiceProvider).
  • Example:
    Menu::macro('admin', function () {
        return Menu::new()
            ->group('Users', function (Menu $menu) {
                $menu->action('UsersController@index', 'List')
                    ->action('UsersController@create', 'Create');
            })
            ->group('Settings', function (Menu $menu) {
                $menu->action('SettingsController@index', 'General')
                    ->action('SettingsController@billing', 'Billing');
            });
    });
    

2. Dynamic Menus with Conditional Logic

  • Use actionIf, actionIfCan, or viewIfCan for permission/state-based items:
    Menu::macro('dashboard', function () {
        return Menu::new()
            ->actionIfCan('reports.index', 'Reports', 'view-reports')
            ->actionIf(request()->user()->isAdmin(), 'AdminController@index', 'Admin Panel')
            ->viewIfCan('notifications.partial', 'Notifications', 'view-notifications');
    });
    

3. Nested Menus with Groups

  • Organize menus hierarchically for complex UIs:
    Menu::macro('sidebar', function () {
        return Menu::new()
            ->group('Content', function (Menu $menu) {
                $menu->action('PostsController@index', 'Posts')
                    ->action('PagesController@index', 'Pages');
            })
            ->group('Settings', function (Menu $menu) {
                $menu->action('SettingsController@general', 'General')
                    ->action('SettingsController@security', 'Security');
            });
    });
    

4. URL Generation Helpers

  • Leverage Laravel’s URL helpers directly:
    Menu::macro('footer', function () {
        return Menu::new()
            ->url('https://example.com/privacy', 'Privacy Policy')
            ->route('terms.show', 'Terms of Service')
            ->action('ContactController@show', 'Contact Us');
    });
    

5. Blade Integration

  • Render menus in Blade with optional classes:
    <nav class="navbar">
        {!! Menu::main()->class('navbar-menu') !!}
    </nav>
    
  • Pass data to menu items:
    Menu::macro('userDropdown', function () {
        return Menu::new()
            ->add('Profile', 'profile', ['data-testid' => 'user-profile'])
            ->add('Logout', 'logout', ['class' => 'text-red-500']);
    });
    

Integration Tips

1. Localization Support

  • Use Laravel’s trans() helper in menu definitions:
    Menu::macro('langSelector', function () {
        return Menu::new()
            ->add(trans('menu.english'), route('lang.switch', 'en'))
            ->add(trans('menu.spanish'), route('lang.switch', 'es'));
    });
    

2. Dynamic Active State

  • Override setActiveFromRequest() for custom logic:
    Menu::macro('customActive', function () {
        return Menu::new()
            ->action('HomeController@index', 'Home')
            ->action('DashboardController@index', 'Dashboard')
            ->setActiveFromRequest(function (string $routeName) {
                return request()->routeIs($routeName) || request()->wantsJson();
            });
    });
    

3. Testing Menus

  • Test macros in PHPUnit:
    public function test_main_menu()
    {
        $menu = Menu::main();
        $this->assertEquals('Home', $menu->first()->title);
        $this->assertTrue($menu->isActive('home'));
    }
    

4. Caching Menus

  • Cache menu definitions in AppServiceProvider:
    Menu::macro('cachedMenu', function () {
        return Cache::remember('menu.sidebar', now()->addHours(1), function () {
            return Menu::new()->action('...')->action('...');
        });
    });
    

5. Headless CMS Integration

  • Fetch menu items from a database:
    Menu::macro('cmsMenu', function () {
        return Menu::new()
            ->items(MenuItem::fromCollection(
                MenuItem::fromCollection(Menu::where('is_active', 1)->get())
            ));
    });
    

Gotchas and Tips

Pitfalls

  1. Active State Conflicts

    • Issue: Multiple routes may match setActiveFromRequest().
    • Fix: Use setActiveFromRequest() with a closure or override the isActive() method:
      Menu::macro('strictActive', function () {
          return Menu::new()
              ->action('home', 'Home')
              ->action('dashboard', 'Dashboard')
              ->setActiveFromRequest(function ($routeName) {
                  return request()->routeIsExact($routeName);
              });
      });
      
  2. Macro Overwriting

    • Issue: Redefining a macro in multiple providers.
    • Fix: Use a single provider (e.g., MenuServiceProvider) or check for macro existence:
      if (!Menu::hasMacro('main')) {
          Menu::macro('main', function () { ... });
      }
      
  3. URL Generation Edge Cases

    • Issue: javascript: links or mailto: links may not render as expected.
    • Fix: Use Link::toUrl() explicitly:
      Menu::macro('utils', function () {
          return Menu::new()
              ->link('mailto:contact@example.com', 'Contact')
              ->link('javascript:alert("Hello")', 'Alert');
      });
      
  4. Blade Escaping

    • Issue: {!! !!} may cause XSS if menu items include user-generated content.
    • Fix: Escape dynamic content or use @verbatim:
      @verbatim
          {!! Menu::dynamicMenu() !!}
      @endverbatim
      
  5. Laravel Version Mismatches

    • Issue: Using actionIfCan with older Laravel versions (pre-5.7).
    • Fix: Use the tuple syntax:
      Menu::macro('legacyCan', function () {
          return Menu::new()
              ->actionIfCan(['view-posts', 'user'], 'Posts', 'PostsController@index');
      });
      

Debugging Tips

  1. Inspect Menu Structure

    • Dump the menu object to debug:
      dd(Menu::main()->toHtml());
      
    • Or use ->toArray() for a structured output:
      dd(Menu::main()->toArray());
      
  2. Check Active State Logic

    • Override isActive() temporarily:
      Menu::macro('debugActive', function () {
          return Menu::new()
              ->action('home', 'Home')
              ->setActiveFromRequest(function ($route) {
                  dd($route, request()->route()->getName());
                  return false;
              });
      });
      
  3. Validate URL Generation

    • Test URL helpers in isolation:
      $this->assertEquals(
          route('home'),
          Menu::new()->action('home', 'Home')->first()->url
      );
      

Extension Points

  1. Custom MenuItem Classes
    • Extend Spatie\Menu\MenuItem for domain-specific logic:
      class PermissionAwareMenuItem extends MenuItem
      {
          public function addPermission(string $permission): self
          {
              $this->data['permission'] = $permission;
              return $this;
          }
      
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport