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

Menumanager Laravel Package

dick/menumanager

A Laravel menu management package for building, ordering, and rendering dynamic navigation menus and nested menu items. Define links to routes or URLs, control hierarchy, and output ready-to-use menus in Blade views, keeping site navigation easy to maintain and customize.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and migrate.

composer require dick/menumanager
php artisan vendor:publish --provider="Dick\MenuManager\MenuManagerServiceProvider"
php artisan migrate

This package predates Laravel's package auto-discovery, so on newer Laravel you may need to register the provider manually in config/app.php (or bootstrap/providers.php on Laravel 11+):

Dick\MenuManager\MenuManagerServiceProvider::class,

Look here first.

  1. The published config (config/menumanager.php or similar) — route prefix, middleware, model binding, template that renders the menu.
  2. The migration — it will reveal the schema you build against (id, name, url, type, target, parent_id, and usually lft/rgt/depth from kalnoy/nestedset).
  3. The model (usually MenuItem) — this is your integration surface; everything else is admin UI.

First use case: log in to Dick Admin, open the menu manager route (typically /admin/menu-item), add two or three top-level items and drag one under another. Then render them in your layout:

@php($menu = \Dick\MenuManager\Models\MenuItem::whereNull('parent_id')->orderBy('lft')->get())
@include('partials.menu', ['items' => $menu])

At that point you have a full loop — editors change the menu without a deploy, you render it in Blade.

Implementation Patterns

Recursive rendering in Blade. The tree is small, so render it as a partial that self-includes. This is the pattern the package expects (and what its own default view does):

{{-- resources/views/partials/menu.blade.php --}}
@if($items->isNotEmpty())
<ul class="menu">
    @foreach($items as $item)
        <li class="{{ $item->is_active ? 'active' : '' }}">
            <a href="{{ $item->url }}" target="{{ $item->target }}">
                {{ $item->name }}
            </a>
            @include('partials.menu', ['items' => $item->children])
        </li>
    @endforeach
</ul>
@endif

Eager-load children once. Fetching root nodes with with('children') avoids N+1 queries when rendering. For deeper nesting pull childrenRecursive (nested set) or eager-load children.children.

Cache the menu, invalidate on write. Menus are write-rare/read-constant, so caching is nearly free:

$menu = Cache::rememberForever('menu.main', fn () => MenuItem::with('children')->whereNull('parent_id')->orderBy('lft')->get());

Hook model events to bust it:

MenuItem::saved(fn () => Cache::forget('menu.main'));
MenuItem::deleted(fn () => Cache::forget('menu.main'));
// also cover restored/moved events if the package uses nested set

Scope by menu name if you need more than one. If the schema has a menu/handle/collection column, filter by it (where('menu', 'footer')) and cache per-scope. If not, either add the column in a migration or maintain "one menu table per menu" via the config — the former is cleaner.

Active state. Don't compare raw strings — URLs drift with trailing slashes, locales, and query strings. Add an accessor:

public function getIsActiveAttribute(): bool
{
    $path = trim(parse_url($this->url, PHP_URL_PATH), '/');
    return $this->url === url()->current()
        || ($path && request()->is($path . '*'));
}

Link to routes, not URLs, when possible. Store the route name (or a type + route column if the schema supports it) and resolve at render time. Hardcoded URLs are the most common source of broken menus after a refactor.

Customize the output. Publish the package views and override the menu partial rather than editing vendor files. Keep your override small — the package's default markup is a starting point, not a contract.

Gotchas and Tips

Nested set columns are the ground truth. If the table has lft/rgt/depth, never write those by hand and never insert raw rows with DB::table(). Use $parent->children()->create(...) or MenuItem::create(['parent_id' => $parent->id, ...]) — otherwise the tree corrupts and drag-and-drop starts behaving erratically. Run MenuItem::fixTree() (nested set) if it already has.

Drag-and-drop needs the package JS. Nesting/reordering is a JavaScript feature. If the admin loads but you can't drag, check that the package's assets are published and included by the admin layout — this is the #1 "the package is broken" report, and it's almost always an asset issue.

Cache is invisible until it isn't. If editors save a menu item and the site doesn't change, the cache invalidation hook is missing or firing on the wrong model. php artisan cache:clear confirms the diagnosis in seconds.

Config caching. After editing config/menumanager.php, run php artisan config:clear; on production the config is baked into the deploy.

Authorization. The manager route is registered under whatever admin middleware the package ships with. Verify a non-admin can't reach /admin/menu-item; if Dick Admin's guard wiring has drifted, add an explicit policy or route middleware.

Old package, newer Laravel. dick/menumanager targets the pre-Backpack, Laravel 5.x era. On modern Laravel expect provider/facade/signature breaks — either pin an older dependency set, fork and maintain, or migrate to backpack/menucrud (the maintained successor by the same author) with a one-time migration of the menu_items table.

Migrations collide with existing schemas. If you already have a menu_items or menus table, publishing the migration will fail on php artisan migrate. Rename the package table in the published migration before running it, and update the model's $table accordingly.

Seeding trees. For deterministic seeds, create parents first and children via $parent->children()->create(...); batch inserts of nested sets with mixed parent/child order produce incorrect lft/rgt values.

Localization. If item names need translating, don't add columns ad hoc — either add a translations table keyed by menu_item_id or duplicate per-locale menus with a locale column and filter at fetch time. Retrofitting either later is painful.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle