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

Laravel Menu Laravel Package

balajidharma/laravel-menu

A Laravel package for building and rendering navigation menus. Define menus and nested items fluently, highlight active routes, add icons or attributes, and output ready-to-use HTML for your layouts. Ideal for admin panels and site navigation.

Deep Wiki
Context7

Getting Started

The package is database-driven: menus and their items live in tables, edited through your app (or via Tinker/seeder for the first pass), not in a config array. That's the key difference from config-based menu libraries like spatie/laravel-menu — you're modeling content, not hardcoding a tree.

Install and scaffold:

composer require balajidharma/laravel-menu
php artisan vendor:publish --provider="BalajiDharma\LaravelMenu\MenuServiceProvider"
php artisan migrate

Where to look first:

  1. The published config/menu.php (table names, model bindings, route/URL resolution settings).
  2. The Menu and MenuItem models the package registers — they define the attributes you'll actually manipulate (name, url, route, target, order, parent_id, is_active, etc.).
  3. The migration stubs, so you know the exact column shapes before seeding.

First use case — seed a header menu and render it:

use BalajiDharma\LaravelMenu\Models\Menu;
use BalajiDharma\LaravelMenu\Models\MenuItem;

$menu = Menu::create(['name' => 'main', 'slug' => 'main']);

$menu->items()->createMany([
    ['title' => 'Home',   'url' => '/',        'order' => 1],
    ['title' => 'Blog',   'route' => 'posts.index', 'order' => 2],
]);
<x-menu name="main" />
{{-- or, depending on your version --}}
@menu('main')

Don't assume the Blade component/helper name — read the README's render section once, because this is the one API surface that changes between versions of the package.

Implementation Patterns

Admin CRUD as the primary integration point. This package shines when you wire it into a Filament/Nova/plain Blade admin. Treat Menu and MenuItem as normal Eloquent models: Menu::where('slug','footer')->first(), then eager-load items.children to avoid N+1 in the renderer.

Parent/child via parent_id. Most database-menu packages use adjacency lists rather than nested sets. Building a tree for display means recursive eager loading:

$menu = Menu::where('slug', 'main')
    ->with(['items' => fn ($q) => $q->whereNull('parent_id')->orderBy('order'),
            'items.children' => fn ($q) => $q->orderBy('order')])
    ->first();

If the package ships a tree helper (e.g. ->tree()), prefer it over hand-rolled recursion — but verify it orders correctly at each level.

Polymorphic linking. For CMS-style sites, link menu items to real models (a Page, a Category) via a menuable morph rather than a raw URL. Store a fallback url so the menu doesn't break when the related model is deleted. If the package doesn't expose the morph, add it: a menuable_type/menuable_id migration on the items table plus a morphTo() in a subclassed MenuItem bound via config.

Route vs URL resolution. For internal links, store the route name + parameters (route, route_params JSON) and resolve at render time. This keeps links alive when slugs change. Reserve raw url for external links and mailto/tel. Set the target/rel attributes on the item, not in the Blade template, so non-developers can control _blank and nofollow.

Multi-menu, multi-location. Use slug (or a location column) as the stable lookup key, never the display name. Your layout references main, footer, account — admins can rename the menu without breaking templates.

Caching. The whole point of the DB is that it changes rarely. Cache the rendered HTML per menu slug and bust it in a model observer:

MenuItem::saved(fn ($i) => Cache::forget("menu:{$i->menu->slug}"));
MenuItem::deleted(fn ($i) => Cache::forget("menu:{$i->menu->slug}"));

Wrap the render call in Cache::remember("menu:{$slug}", 3600, fn () => ...). This removes the query cost from every page request and makes the package production-viable.

Authorization. Don't expose menu editing to every authenticated user. Gate the admin routes on a permission (menu.manage) and validate parent_id on update — a malicious or buggy request can set an item as its own ancestor and produce an infinite render loop.

Gotchas and Tips

  • Circular parents crash the renderer. Validate on save that the new parent_id isn't the item itself or a descendant. Recursive tree builders will segfault/stack-overflow otherwise. Add this check regardless of what the package does.
  • Ordering is per-level, not global. order = 5 under two different parents are independent sequences; a drag-and-drop admin needs to reorder siblings, not the flat table. Keep an order integer and accept gaps (1, 2, 3, 7) rather than renumbering on every move.
  • Soft deletes on items leak children. If you soft-delete a parent, its children may still render (or vanish depending on the query). Decide explicitly: cascade-delete children in a model event, or filter orphaned items at query time.
  • N+1 on children is the #1 performance issue. Any menu deeper than one level needs recursive eager loading or a cached render. Profile with DB::enableQueryLog() on a page with a header, footer, and sidebar menu.
  • Config table names must match migrations. If you publish config but not migrations (or vice versa), or change table_names after migrating, you'll get cryptic "table not found" errors on relationship loads. Re-publish and re-run migrations when changing this.
  • Route/model bindings in config. If the package lets you swap the Menu/MenuItem classes, you must also update the morph map and any resolveRelationUsing calls — otherwise menu->items returns base models without your added columns.
  • Rendering is not escaped by default in some versions. If item title is admin-supplied HTML (e.g. an icon + label), confirm the renderer's escaping behavior before letting untrusted users into the admin. Prefer a separate icon column over {!! !!}.
  • URL generation in queued/cached contexts. If you cache rendered HTML containing route() output and later change APP_URL or deploy behind a different asset host, cached menus keep stale absolute URLs. Cache the resolved tree or relative paths, not the final HTML, if the domain changes.
  • Testing. Menu rendering is trivially testable: assert the seeded tree renders expected links and order. Include a test for the circular-parent validation and for empty-menu fallback (render nothing rather than a broken <ul></ul>).
  • Extension points. The most useful hooks are model observers (saved/deleted), the config's model class bindings, and any published Blade views — publish the views to customize markup without forking; only subclass the models when you need new columns or relationships.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator