Weave 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

Menus Laravel Package

caffeinated/menus

caffeinated/menus is a Laravel package for creating dynamic, database-driven menus. Build nested items, control ordering, link to routes or URLs, and render configurable menus in Blade with ease.

Deep Wiki
Context7

Getting Started

Install and confirm compatibility first. The package pins Laravel versions from a 2021 release, so on Laravel 9+/PHP 8.x check the constraint before anything else:

composer require caffeinated/menus
composer why-not caffeinated/menus   # if resolution fails, inspect the constraint

Service provider and Menu facade are auto-discovered on Laravel 5.5+. If you're on an older or non-standard setup, register Caffeinated\Menus\MenusServiceProvider in config/app.php and alias the facade.

Where to look first. Read these three classes in vendor/caffeinated/menus/src before writing anything:

  • Menus.php — the manager/registry behind the facade (make, get, exists, render).
  • Menu.php — the named menu container: add(), attribute helpers, render().
  • Item.php — a single link: url, route, raw, attr, isActive, nesting via add().

Then look at the package's Blade views (menu wrapper + item partial). Those two files define the markup and the class names you'll be targeting with CSS, so skim them before you start styling.

First use case — the primary nav. Define it once in a service provider or, better, a view composer so it's available to your layout:

use Caffeinated\Menus\Facades\Menu;

Menu::make('primary', function ($menu) {
    $menu->add('Home', '/');
    $menu->add('About', '/about');
    $menu->add('Contact', '/contact');
});

Render it in layouts/app.blade.php:

{!! Menu::render('primary') !!}

That's the whole loop: define → store under a name → render by name. Every other feature is an extension of those three steps.

Implementation Patterns

Build menus in a view composer, not a controller. Menus are shared across layouts, so bind them once:

View::composer('layouts.app', function ($view) {
    if (! Menu::exists('primary')) {
        Menu::make('primary', function ($menu) { /* ... */ });
    }
});

Guard with Menu::exists() — the registry persists for the request, and a second make() on the same name will either overwrite or double up depending on version.

Nesting: capture the parent item and call add() on it. This is the cleanest, least magic-dependent form:

$about = $menu->add('About', '/about');
$about->add('Who We Are', '/about/who-we-are');
$about->add('What We Do', '/about/what-we-do');

$services = $menu->add('Services', '/services');
$services->add('Design', ['route' => 'services.design']);
$services->add('Development', ['route' => 'services.development']);

Prefer route names over hard-coded URLs for anything internal — it survives URL changes and makes active-state detection far more reliable.

Drive menus from config or the database, not literals. A config/navigation.php array of ['title', 'route', 'children' => [...], 'order' => 10] lets you sort by key and loop:

collect(config('navigation.primary'))
    ->sortBy('order')
    ->each(fn ($link) => $menu->add($link['title'], ['route' => $link['route']]));

This also gives you a cacheable representation (see Gotchas).

Attributes and presentation. Set them on the item, not in Blade:

$menu->add('Docs', ['route' => 'docs'])
     ->attr('class', 'nav-link')
     ->attr('target', '_blank');

Do class/id/data work at definition time so all markup decisions live in one file, and the Blade view stays a dumb recursive renderer.

Raw HTML for things that aren't links. Badges, dividers, a logout button with a CSRF-hidden form:

$menu->add('Divider', ['raw' => '<hr class="nav-divider">']);
// or
$menu->add('Notifications', ['raw' => view('partials.nav-badge', $data)->render()]);

Filter by authorization before adding. Menus leak admin links if you filter only in the view:

if (Gate::allows('view-reports')) {
    $menu->add('Reports', ['route' => 'reports.index']);
}

Active states. Items expose an active check you can use in the default view, and typically a child-aware variant so a parent stays highlighted while a sub-page is open. Rely on that for active/open classes rather than comparing request()->url() by hand in Blade — the package already handles route-based matching.

Custom rendering. Publish or override the views under resources/views/vendor/caffeinated/menus/, keep the same variable names the package passes in, and recurse over children in the item partial. Add your own wrapper markup there instead of post-processing the output string.

Gotchas and Tips

The package is dormant (last release 2021). Expect friction on newer Laravel: removed str_*/array_* helpers, stricter facade auto-discovery, PHP 8.1 deprecations. Do a smoke test (define + nest + render) on your exact version before committing to it, and be prepared to fork and patch a small file rather than wait for an upstream fix.

Active detection is string/route based — be deliberate. Trailing slashes, query strings, locale prefixes, and wildcard routes (/blog/*) are the usual sources of "nothing is ever active" or "everything is active." Test with a URL that has a query string attached. If it misbehaves, stop fighting it: add an explicit attribute and compare route names yourself in the view.

raw() is an XSS hole if you feed it user data. Only pass strings you generated (rendered Blade partials, static markup). Never pass a menu title from the database through raw.

Build-once semantics. The registry holds the built menu for the request. Defining inside a loop over models, or in a partial that renders twice, silently duplicates items. Use Menu::exists() guards, or reset/rebuild explicitly when the menu is context-dependent (per-tenant, per-locale).

Closures don't cache. If you cache the navigation structure in Redis, cache the plain array of links and rebuild the Menu object from it per request. Serializing a menu built from closures will fail or produce garbage.

Order is insertion order. There's no magic sort unless you apply one; if the design demands a specific order (dividers between groups, CTA last), encode it in your data structure and sort before calling add(), otherwise a config reshuffle silently reorders the nav.

Facade name collisions. Menu is a popular alias — Spatie's menu package, various admin panels. If you get "class not found" or calls hitting the wrong object, fully qualify Caffeinated\Menus\Facades\Menu or inject the manager instead of aliasing.

Debugging: dump the built tree before blaming the view — dd(Menu::get('primary')->getItems()) shows exactly what was added, in what order, with what attributes. Nine times out of ten the bug is a menu defined on the wrong composer or a name typo (exists('primary') vs make('main')), not the renderer.

Extension points worth knowing: the published Blade views (markup/classes), the Item model-level helpers (add your own active logic by extending or wrapping), and the config array feeding make() (swap to a DB repository later without touching your layout).

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource