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

Menu Laravel Package

konekt/menu

Konekt Menu is a Laravel package for building and rendering hierarchical menus with an expressive PHP API. It supports submenus, icons, permissions, active-state detection, and Blade rendering, making it easy to define navigation once and reuse it throughout your application.

View on GitHub
Deep Wiki
Context7

Getting Started

konekt/menu is a small, framework-native menu builder: you register named menus and items in PHP, then render them in Blade. It is not a full navigation/CMS system — think "structured array of links with active-state detection," not an admin menu UI.

Install and confirm discovery:

composer require konekt/menu

Laravel 5.5+ auto-discovers Konekt\Menu\MenuServiceProvider; on older versions add it to config/app.php, and register the Menu facade if you want the static API.

Where to look first, in order:

  1. README.md — the API surface is tiny enough that the readme is effectively the manual.
  2. src/Menu.php, src/Item.php, src/Link.php — these three files tell you everything about attributes, nesting, and active-state logic.
  3. src/MenuServiceProvider.php — what is bound in the container and whether anything is configurable/publishable.

First use case: a top navbar. In AppServiceProvider::boot() (or a dedicated MenuServiceProvider):

use Konekt\Menu\Facades\Menu;

Menu::create('navbar');
Menu::get('navbar')->addItem('home', 'Home')->route('home');
Menu::get('navbar')->addItem('about', 'About')->url('/about');

Then in resources/views/layouts/app.blade.php:

{!! Menu::get('navbar')->render() !!}

That is the whole loop: define once at boot, render once in the layout. Start here before writing any custom markup — it validates that your service provider registration and active-state logic work.

Implementation Patterns

Centralize registration, one provider. Put every menu definition in a single MenuServiceProvider rather than scattering addItem() calls across controllers. Controllers should never build menus; they should only choose which menu to render. This keeps definitions greppable and testable.

Route-based links over URLs. ->route('users.show', ['user' => $user->id]) beats ->url('/users/'.$user->id) — it survives route renames and gives the package a reliable hook for active detection. Reserve ->url() for external links and assets.

Nesting for dropdowns. Build the parent first, then attach children to the returned item:

$admin = Menu::get('navbar')->addItem('admin', 'Admin')->icon('fa fa-cog');
$admin->addSubItem('users', 'Users')->route('admin.users.index');
$admin->addSubItem('roles', 'Roles')->route('admin.roles.index');

The parent item becomes the dropdown toggle; use ->data('toggle', 'dropdown') and ->attribute('class', 'dropdown-toggle') to match Bootstrap, or drop the renderer entirely and iterate $menu->items yourself.

Conditional visibility belongs at registration. Gate items by permission at boot time, not in the view:

if (Auth::check() && Auth::user()->can('view-reports')) {
    Menu::get('navbar')->addItem('reports', 'Reports')->route('reports.index');
}

Because menus are built per-request, this is cheap and avoids leaking link markup for unauthorized users.

Custom rendering. render() emits a conventional <ul>/<li> tree; when your design system needs different markup, loop the collection and use the accessors:

@foreach (Menu::get('sidebar')->items as $item)
    <a href="{{ $item->url }}" class="{{ $item->isActive() ? 'active' : '' }}">
        {{ $item->label }}
    </a>
@endforeach

This is the single most useful escape hatch: you get the active-state logic and link resolution without the package's HTML.

Modular apps (Concord or similar). Each module registers its own items in its service provider's boot() against a shared menu name. Rely on insertion order for grouping — module boot order effectively determines menu order, so document that if ordering matters.

Testing. Assert on the menu object, not the rendered HTML: Menu::get('navbar')->hasItem('reports'), $item->isActive(), $item->url. HTML assertions break on every markup tweak.

Gotchas and Tips

create() vs get(). Creating a menu twice in one request can reset or duplicate items depending on version. Guard with a null check (if (!Menu::exists('navbar'))) or, better, register each menu exactly once in one provider. get() on an unregistered name throws — a common failure when a package or module forgets to create() first.

Active detection is match-based, not intent-based. It compares the current request against the item's resolved URL/route. Trailing slashes, query strings, locale prefixes, and index vs show routes all undermine it. For parent items that own a section, explicitly activate: $admin->activate() when the current route starts with admin.. Where the package supports it, prefer ->activateOnRoute('admin.*')-style patterns over URL guessing.

Lazy route resolution. ->route() resolves the URL when rendered. A route whose required parameter is missing (or model-bound to null) throws at render time in the layout — far from where the item was defined. Define items with route params already available, or wrap model-dependent items in an if.

Rendering is unescaped HTML. You must use {!! !!}. Corollary: any user-supplied string passed as a label or attribute is an XSS vector — escape labels before handing them to the builder, or render manually with {{ }}.

Ordering is insertion order only. There is no sort/priority attribute you can rely on. Reorder the registration calls, or collect items into your own array and sort before rendering.

Menus are rebuilt every request. Fine for a handful of items; if you generate hundreds (e.g. a full sitemap), cache the structure and re-evaluate only active state per request. Do not cache rendered HTML wholesale — active classes are user- and URL-specific.

Config and config:cache. If the package exposes a config file, publish it with vendor:publish and read config lazily inside the provider, not at file load. Anything resolved in a service provider's constructor can behave differently under php artisan config:cache.

Extension points. The menu and item classes are plain PHP; subclass Item to add accessors (e.g. badgeCount()), and check whether MenuFactory/container bindings let you swap the class. That is usually cleaner than post-processing rendered HTML with str_replace.

Know when not to use it. With ~28 stars and low activity, treat it as a thin utility you may need to read and patch. If your needs grow past "named list of links with an active flag," a 40-line Blade component backed by an array will be easier to maintain and debug than extending this package.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route