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. Add links via routes/actions/URLs, customize attributes and classes, and automatically set active items from the current request. Includes macros for reusable menu builders.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require spatie/laravel-menu
    

    Publish the config (if needed) with:

    php artisan vendor:publish --provider="Spatie\Menu\MenuServiceProvider"
    
  2. Define a Menu Macro: Register a reusable menu in a service provider (e.g., AppServiceProvider):

    use Spatie\Menu\Menu;
    use Spatie\Menu\Laravel\Facades\Menu as MenuFacade;
    
    Menu::macro('main', function () {
        return Menu::new()
            ->action('HomeController@index', 'Home')
            ->action('AboutController@index', 'About')
            ->action('ContactController@index', 'Contact')
            ->setActiveFromRequest();
    });
    
  3. Render in Blade:

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

First Use Case

Create a header navigation menu for a blog:

// app/Providers/AppServiceProvider.php
Menu::macro('blogHeader', function () {
    return Menu::new()
        ->url('/blog', 'Blog')
        ->action('PostController@index', 'Posts')
        ->url('/blog/tags', 'Tags')
        ->setActiveFromRequest();
});

Blade:

<header>
    {!! Menu::blogHeader() !!}
</header>

Implementation Patterns

Core Workflows

  1. Menu Composition:

    • Chaining Methods: Build menus fluently with methods like url(), action(), html(), or view().
      Menu::new()
          ->url('/dashboard', 'Dashboard')
          ->action('AdminController@index', 'Admin')
          ->html('<li>Static Item</li>', 'Static');
      
    • Nested Menus: Use ->add() with sub-menus:
      Menu::new()
          ->add('Products')
          ->add('Services')
          ->add('Support')
              ->add('FAQ')
              ->add('Contact');
      
  2. Dynamic Active States:

    • Auto-detect active items via setActiveFromRequest() (matches current route/URL).
    • Custom logic with setActive():
      ->action('ProfileController@edit', 'Profile')
          ->setActive(fn () => Auth::check());
      
  3. Conditional Items:

    • Authorization: Use addIfCan with Laravel Gates/Policies:
      ->addIfCan('Users', 'view users', 'Users')
          ->action('UserController@index', 'Manage Users');
      
    • Request Conditions: Use urlIf, actionIf:
      ->urlIf(request()->has('promo'), '/promo', 'Promo');
      
  4. Reusable Macros:

    • Define menus once, reuse across views:
      Menu::macro('footer', function () {
          return Menu::new()
              ->url('/privacy', 'Privacy')
              ->url('/terms', 'Terms')
              ->url('/sitemap', 'Sitemap');
      });
      

Integration Tips

  • Blade Directives: Extend Blade with custom directives for menus:

    Blade::directive('menu', function ($expression) {
        return "<?php echo \\Spatie\\Menu\\Laravel\Facades\\Menu::{$expression}(); ?>";
    });
    

    Usage:

    @menu('blogHeader')
    
  • Middleware: Dynamically modify menus based on user roles:

    public function handle(Request $request, Closure $next) {
        if (auth()->check()) {
            Menu::macro('main', fn () => Menu::new()
                ->action('DashboardController@index', 'Dashboard')
                ->action('ProfileController@edit', 'Profile'));
        }
        return $next($request);
    }
    
  • API-Driven Menus: Fetch menu items from a database:

    Menu::macro('apiMenu', function () {
        $items = MenuItem::where('active', true)->get();
        return Menu::new()->items($items->map(fn ($item) =>
            Menu::item()->url($item->path)->title($item->name)
        ));
    });
    
  • Localization:

    Menu::macro('i18nMenu', function () {
        return Menu::new()
            ->url('/en', trans('menu.english'))
            ->url('/es', trans('menu.spanish'));
    });
    

Gotchas and Tips

Pitfalls

  1. Active State Conflicts:

    • setActiveFromRequest() uses Laravel’s Request::is() under the hood. Overlapping routes (e.g., /posts and /posts/*) may cause unexpected active states.
    • Fix: Use setActive() with custom logic or adjust route patterns.
  2. Macro Overwriting:

    • Macros registered later overwrite earlier ones. Use descriptive names (e.g., adminSidebar) to avoid collisions.
  3. URL Generation Issues:

    • action() and route() methods require valid Laravel route names/controllers. Invalid routes throw InvalidArgumentException.
    • Tip: Validate routes in tests:
      $this->get('/invalid')->expectException(\InvalidArgumentException::class);
      
  4. Blade Escaping:

    • Menu::toHtml() escapes output by default. Use ->toHtml(false) for raw HTML (e.g., for JavaScript-generated menus).
  5. Caching Quirks:

    • Macros are not cached by default. Cache the entire menu output if performance is critical:
      Cache::remember('menu.main', now()->addHours(1), fn () => Menu::main());
      

Debugging

  • Inspect Menu Structure: Use dd(Menu::new()->url('/test', 'Test')->toHtml()) to debug the generated HTML structure.
  • Check Active States: Temporarily add ->setActive(true) to test items to verify logic.

Extension Points

  1. Custom Menu Items: Extend the Spatie\Menu\MenuItem class for specialized items (e.g., dropdowns with icons):

    class IconMenuItem extends MenuItem {
        public function icon($icon): self {
            $this->data['icon'] = $icon;
            return $this;
        }
    }
    

    Usage:

    Menu::new()->add((new IconMenuItem())->url('/dashboard', 'Dashboard')->icon('fas fa-tachometer'));
    
  2. Blade Components: Replace toHtml() with a Blade component for complex rendering:

    Menu::macro('componentMenu', function () {
        return new \Spatie\Menu\MenuItemCollection([
            Menu::item()->url('/home', 'Home')->setActive(true),
        ]);
    });
    

    Blade:

    <x-menu :items="Menu::componentMenu()" />
    
  3. Event Listeners: Dynamically modify menus via events (e.g., Illuminate\Auth\Events\Login):

    public function handle(Login $event) {
        Menu::macro('userMenu', fn () => Menu::new()
            ->action('ProfileController@edit', 'Profile')
            ->action('LogoutController@store', 'Logout'));
    }
    

Performance Tips

  • Lazy-Loading: Defer menu building until needed:

    Menu::macro('lazyMenu', function () {
        return fn () => Menu::new()->url('/heavy', 'Heavy')->toHtml();
    });
    

    Blade:

    {!! Menu::lazyMenu()() !!}
    
  • View Caching: Cache Blade views containing menus:

    @cache(['menu', auth()->id()])
        {!! Menu::main() !!}
    @endcache
    

Laravel-Specific Quirks

  • Route Caching: Ensure php artisan route:cache is run if using route-based menus to avoid runtime route resolution delays.
  • Service Provider Order: Register macros in a provider that loads after MenuServiceProvider to avoid ClassNotFoundException.
  • PHP 8 Features: Leverage named arguments for clarity:
    ->action(
        controller: 'PostController',
        method: 'index',
        title: 'Posts',
        active: true
    );
    
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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony