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

terranet/menus

Terranet Menus is a Laravel package for building and managing database-backed navigation menus. Define menus and nested items, order them, link to routes or URLs, and render them in your Blade views.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package via Composer and register the service provider (it predates package auto-discovery, so this step is manual in older setups):

composer require terranet/menus

Add the provider to config/app.php (or bootstrap/providers.php on Laravel 11+) if auto-discovery does not pick it up:

Terranet\Menus\Providers\MenuServiceProvider::class,

Publish and run the migrations, plus any config file that ships with the package:

php artisan vendor:publish --provider="Terranet\Menus\Providers\MenuServiceProvider"
php artisan migrate

Where to look first: open the published migration to understand the schema — this tells you the real column names (title, url, parent_id, order/position, target, active) far faster than the README, which is thin. Then read the Menu model for scopes, accessors, and any tree helpers.

First use case: a database-driven navigation bar. Create a couple of root items and a child, then render them.

use Terranet\Menus\Models\Menu;

Menu::create(['title' => 'Home',      'url' => '/',        'parent_id' => null, 'order' => 1]);
Menu::create(['title' => 'About',     'url' => '/about',   'parent_id' => null, 'order' => 2]);
Menu::create(['title' => 'Team',      'url' => '/about/team', 'parent_id' => 2,  'order' => 1]);

Render in a Blade partial by iterating the tree once you've loaded it:

@foreach ($menus as $item)
    <a href="{{ url($item->url) }}">{{ $item->title }}</a>
    @if ($item->children->isNotEmpty())
        @foreach ($item->children as $child)
            <a href="{{ url($child->url) }}">{{ $child->title }}</a>
        @endforeach
    @endif
@endforeach

Implementation Patterns

Load the tree in one query, not N. The single biggest day-to-day pattern with a parent_id-based menu is avoiding recursive queries. Fetch all rows once, group by parent_id, and build the hierarchy in memory:

$flat = Menu::orderBy('order')->get()->groupBy('parent_id');

$build = function ($parentId) use (&$build, $flat) {
    return collect($flat->get($parentId, []))->map(function ($item) use (&$build) {
        $item->setRelation('children', $build($item->id));
        return $item;
    })->values();
};

$nav = $build(null);

If the package already defines children() / parent() relations, use them with with('children'), but only for known-depth menus — deep eager loading multiplies queries.

Cache the rendered tree. Menus change rarely and render on every request. Cache the built structure (not the HTML, so you can vary markup per layout) and key it by a version you bump on save:

$nav = Cache::rememberForever('menu.main', fn () => $build(null));

Add a model observer on saved/deleted to Cache::forget('menu.main').

Keep content and presentation separate. Store only title, url, parent_id, order, target. Do not bake in icons, CSS classes, or permissions as new columns — use a convention table or a meta JSON column the package likely supports, so a designer can restyle without a migration.

Programmatic vs. admin-driven menus. For small static sites, seed menus in a MenuSeeder and rebuild the cache in php artisan db:seed. For CMS-style sites, wire the package's model into your own CRUD resource (Filament, Nova, or hand-rolled Blade). The package gives you the model, not the UI — do not expect an admin screen out of the box.

Route integration. Prefer storing route names or slugs over full URLs. A url accessor that calls route($this->route_name) keeps links working across environments where the host differs.

Blade partial per depth. Recursively include a partial for dropdowns instead of nesting foreach three levels deep — it stays readable when the menu grows.

Gotchas and Tips

  • Laravel 5 legacy. Last release was late 2020 and the description targets Laravel 5. On Laravel 8+, watch for $dates, old Str/Arr helpers, and ServiceProvider::provides() mismatches. Add the provider manually if auto-discovery fails silently.
  • Manual provider registration is easy to forget — the classic symptom is a Class "Terranet\Menus\..." not found or a missing table because migrations never ran.
  • order is a reserved word in MySQL. If the column is literally order, always qualify it (menu_items.order) in raw queries or it will parse as ORDER BY.
  • No dependents means no community answers. Debug by reading the source in vendor/terranet/menus/src — you will not find Stack Overflow threads. Log the SQL with DB::listen to confirm the queries the relations generate.
  • N+1 is the default trap. Any $menu->children inside a Blade loop without eager loading will fire one query per row. Confirm with a query counter (Laravel Debugbar or a DB::listen count) before shipping.
  • Cache invalidation. If you cache the tree, remember parent_id changes on a single row invalidate the whole tree — forget the entire menu key, not one item's key.
  • URL security. If users can enter free-text url values, validate them (allow only /-relative paths or http(s)), otherwise you get stored javascript: links. Escape titles with {{ }} — never {!! !!} unless you control the input.
  • Cycles. Nothing in a simple parent_id schema prevents setting an item's parent to its own descendant, which will hang your in-memory tree builder. Guard on save in the controller/observer.
  • Extension point: the model is a plain Eloquent class, so override it via a custom model + config binding or extend it to add scopes like scopeActive and accessors like getDepthAttribute() rather than editing vendor files.
  • Trash the package if the menu is static. For a handful of fixed links, a Blade partial with hard-coded routes is faster, cache-friendly, and has zero migration/maintenance cost. This package earns its place only when non-developers edit menus.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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