Menus and Navigation Sources

Public navigation is data: menus and items created in Appearance → Menus. A plugin extends it by offering link sources in the menu builder and, if it ships a theme, by rendering menus with its own templates. (The admin sidebar is a different system — see Admin menu and dashboard.)

In the user guide: Menus.

Data model

ModelKey columns / behaviour
Menu (menus)name. items() = top-level rows by sort; allItems() = every row. Extends BaseModel: changing a menu or item clears its tree/fit/rendered caches and the response cache
MenuItem (menu_items)menu_id, parent_id (nesting), sort, label, type (custom | route | resource), link (custom URL), route + parameters (JSON; the key _ui holds mega-menu presentation), resource_type/resource_id (morphTo), target, css_class, icon, is_mega, title, condition

MenuItem::getUrl() resolves by type: a resource item uses the model's getUrl() method or url attribute; a route item calls route($route, $parameters) (a missing route gives #); a custom item uses link. Because routes are stored by name, do not rename or remove a public route that may be in someone's menu.

Rendering a menu

{!! menu('Main', 'bootstrap', ['class' => 'ms-auto', 'icon' => true]) !!}   {{-- by name or numeric id --}}
{!! menu($id, 'footer') !!}
@php($tree = menu('Main', '_json'))                                    {{-- the processed item collection, no HTML --}}

Add your content to the menu builder

The builder (Menus screen) shows one collapsible container per registered source: Pages, User menu, Posts, Post categories, Tags (core) plus Booking and TruCommerce ones. Items you offer are templates: when the administrator ticks them and clicks Add to menu their data (label, type, route, parameters, resource…) is copied into menu_items rows.

Modules/Playground/app/Helpers/PlaygroundBuilderMenu.php

use App\Facades\MenuBuilder;
use App\Services\Menu\MenuContainer;
use App\Services\Menu\MenuItem;

MenuBuilder::register('playground', function (MenuContainer $menu): void {
    $menu->name('Playground')->key('playground-menu');       // container title and DOM key
}, 80);                                                       // order among containers

// The item list can be large: only build it on the builder screen itself.
if (! Str::contains(request()->route()?->getName() ?? '', 'admin.menus')) {
    return;
}

$container = MenuBuilder::get('playground');

$container->item('playground-home', static function (MenuItem $item): void {
    $item->label('Playground home')->key('playground-home')->target('_self')->route('playground.index');
});

Note::query()->latest('id')->limit(50)->get()->each(function (Note $note) use ($container): void {
    $container->item('playground-note-' . $note->id, static function (MenuItem $item) use ($note): void {
        $item->label($note->title)->key('playground-note-' . $note->id)->target('_self')
            ->route('resolve.show', ['slug' => $note->slug]);        // slug-resolved pages use resolve.show
    });
});

Call it from your AdminBooting listener. The container then appears on /admin/menus, and adding items from it (admin.menus.add-items) creates MenuItem rows whose getUrl() resolves to your route.

MenuItem (builder template) methodMeaning
label($text), key($id), target('_self'|'_blank')Display text, the item's id inside the container (what the form posts), link target
route($name, array $params = [])Type route. Prefer this for plugin pages
resource(Model::class, $id)Type resource (morph): the URL is taken from the model (getUrl() or url). Use for CMS pages (the core Pages container does, or the page-type route when the page has one)
url($string)Type custom
type(), classes(), icon(), tag(), priority()Presentation and ordering

MenuBuilder API: register($key, $callable, ?int $order), get($key), find(), all() (sorted by priority). MenuContainer: name(), key(), priority(), item($key, $callable), items([...]), hasItems().