Skip to main content

Overview

The Menu Management system provides flexible navigation control with support for multiple menus, nested items (parent-child relationships), custom ordering, and various menu locations throughout your site.

Database Structure

Model Relationships

class Menu extends Model
{
    protected $fillable = ['name'];

    // Get all items in this menu, ordered by position
    public function items()
    {
        return $this->hasMany(MenuItem::class)->orderBy('order');
    }
}
Key Points:
  • Each menu has many items
  • Items automatically ordered by order field
  • Unique menu names prevent duplicates
Reference: app/Models/Menu.php:7
class MenuItem extends Model
{
    protected $fillable = [
        'menu_id', 'label', 'url', 'parent_id', 'order'
    ];

    // Parent menu container
    public function menu()
    {
        return $this->belongsTo(Menu::class);
    }

    // Child items (submenu)
    public function children()
    {
        return $this->hasMany(MenuItem::class, 'parent_id')
            ->orderBy('order');
    }

    // Parent item (for nested items)
    public function parent()
    {
        return $this->belongsTo(MenuItem::class, 'parent_id');
    }
}
Relationships:
  • Menu: Each item belongs to one menu
  • Parent: Optional parent item for nesting
  • Children: Child items ordered by position
Reference: app/Models/MenuItem.php:7

Hierarchical Organization

Menus support unlimited nesting levels:
Primary Menu
├── Home
├── Products
│   ├── Software
│   ├── Hardware
│   └── Services
│       ├── Consulting
│       └── Support
├── About
│   ├── Team
│   └── History
└── Contact
Implementation:
// Top-level item
$products = MenuItem::create([
    'menu_id' => $menuId,
    'title' => 'Products',
    'url' => '/products',
    'parent_id' => null,
    'order' => 2
]);

// Nested item
$software = MenuItem::create([
    'menu_id' => $menuId,
    'title' => 'Software',
    'url' => '/products/software',
    'parent_id' => $products->id,
    'order' => 1
]);

User Workflows

  1. Define Menu Container
    • Create new menu with unique name
    • Example: “Primary Navigation”, “Footer Links”
  2. Choose Menu Location
    • Set menu_group to identify placement:
      • primary - Main navigation header
      • footer - Footer links
      • sidebar - Sidebar navigation
      • mobile - Mobile-specific menu
  3. Add to Theme
    • Reference menu in Blade templates
    • Apply appropriate styling
Database Entry:
Menu::create(['name' => 'Primary Navigation']);

Item Creation Workflow

  1. Enter Item Details
    • Title: Display text (e.g., “Home”, “About Us”)
    • URL: Link destination (e.g., /about, https://example.com)
    • Order: Position in menu (lower numbers first)
  2. Configure Behavior
    • Target: _self (same window) or _blank (new tab)
    • Icon: Optional CSS class (e.g., fa-home, bi-house)
  3. Set Parent (Optional)
    • Leave parent_id null for top-level items
    • Set parent_id to create dropdown/submenu
  4. Assign to Menu
    • Link to parent menu via menu_id
    • Set menu_group for organization
Example:
MenuItem::create([
    'menu_id' => 1,
    'title' => 'About Us',
    'url' => '/about',
    'parent_id' => null,
    'order' => 3,
    'target' => '_self',
    'icon_class' => 'fa-info-circle',
    'menu_group' => 'primary'
]);

Drag-and-Drop Ordering

The order field controls item sequence:Updating Order:
// Update multiple items after drag-and-drop
foreach ($newOrder as $index => $itemId) {
    MenuItem::where('id', $itemId)->update(['order' => $index]);
}
Automatic Ordering:
  • Items retrieved with orderBy('order')
  • Lower numbers appear first
  • Equal numbers ordered by creation date
Best Practice:
  • Use increments of 10 (10, 20, 30) for easy insertion
  • Renumber after major reordering
  1. Create Parent Item
    • Add top-level menu item
    • Note the item ID
  2. Add Child Items
    • Create new items with parent_id set to parent item ID
    • Set unique order values for each child
  3. Recursive Loading
    // Load menu with all nested children
    $menu = Menu::with('items.children')->find(1);
    
Example Structure:
// Parent
$services = MenuItem::create([
    'title' => 'Services',
    'url' => '#',
    'parent_id' => null,
    'order' => 10
]);

// Children
MenuItem::create([
    'title' => 'Web Development',
    'url' => '/services/web',
    'parent_id' => $services->id,
    'order' => 1
]);

MenuItem::create([
    'title' => 'Mobile Apps',
    'url' => '/services/mobile',
    'parent_id' => $services->id,
    'order' => 2
]);

Frontend Display

Rendering Menus

Basic menu without nesting:
@php
    $menu = \App\Models\Menu::where('name', 'Primary Navigation')
        ->with('items')
        ->first();
@endphp

<nav>
    <ul>
        @foreach($menu->items as $item)
            <li>
                <a href="{{ $item->url }}" target="{{ $item->target ?? '_self' }}">
                    @if($item->icon_class)
                        <i class="{{ $item->icon_class }}"></i>
                    @endif
                    {{ $item->title }}
                </a>
            </li>
        @endforeach
    </ul>
</nav>
The menu_group field allows multiple menu sets:

Primary Navigation

Group: primary
  • Main site header menu
  • Product categories
  • Key pages

Footer Menu

Group: footer
  • Company info links
  • Legal pages
  • Social media

Sidebar Menu

Group: sidebar
  • Category filters
  • Quick links
  • User dashboard

Mobile Menu

Group: mobile
  • Simplified navigation
  • Touch-optimized
  • Hamburger menu
Usage:
$footerMenu = Menu::where('name', 'Footer Links')
    ->whereHas('items', function($query) {
        $query->where('menu_group', 'footer');
    })
    ->with(['items' => function($query) {
        $query->where('menu_group', 'footer');
    }])
    ->first();

Advanced Features

Adding Icons to Menu Items

The icon_class field supports any icon library:Font Awesome:
'icon_class' => 'fas fa-home'
Bootstrap Icons:
'icon_class' => 'bi bi-house-door'
Blade Template:
@if($item->icon_class)
    <i class="{{ $item->icon_class }} me-2"></i>
@endif
{{ $item->title }}

Highlighting Current Page

Detect active menu items:
@foreach($menu->items as $item)
    <li class="{{ request()->is($item->url) ? 'active' : '' }}">
        <a href="{{ $item->url }}">
            {{ $item->title }}
        </a>
    </li>
@endforeach
Advanced Matching:
@php
    $isActive = request()->url() === url($item->url) ||
                request()->is(trim($item->url, '/') . '/*');
@endphp

<li class="{{ $isActive ? 'active' : '' }}">

Database Constraints

Foreign Key Cascade

Deleting a menu item cascades to children:
$table->foreign('parent_id')
    ->references('id')
    ->on('menu_items')
    ->onDelete('cascade');
Behavior:
  • Deleting parent item removes all children
  • Prevents orphaned menu items
  • Maintains data integrity
Reference: database/migrations/2025_05_25_175649_create_menu_items_table.php:24

Unique Constraints

Menu Names: Must be unique
$table->string('name')->unique();
Prevents:
  • Duplicate menu containers
  • Ambiguous menu references

Best Practices

Performance

Eager Loading:
// Load menu with all nested items
Menu::with('items.children.children')->find(1);
Caching:
Cache::remember('menu.primary', 3600, function() {
    return Menu::where('name', 'Primary')
        ->with('items')
        ->first();
});

Organization

Naming Convention:
  • Use descriptive menu names
  • Consistent menu_group values
  • Document custom groups
Ordering Strategy:
  • Use increments of 10
  • Reserve gaps for insertion
  • Periodic renumbering

Common Use Cases

  • E-commerce: Product category navigation
  • Corporate Sites: Multi-level department structure
  • Documentation: Sidebar navigation with nested sections
  • Multi-language: Different menus per locale
  • User Roles: Dynamic menus based on permissions

Build docs developers (and LLMs) love