offload-project/laravel-navigation
Laravel Navigation helps you define, organize, and render application menus and navigation trees with a fluent PHP API and Blade components, including active-state handling and flexible link/group structures for Laravel apps.
offload-project/laravel-navigation is a config-driven navigation/menu builder for Laravel: you declare menus (items, labels, URLs, ordering, visibility) and render them in Blade instead of hand-maintaining link lists in layouts.
Install and publish:
composer require offload-project/laravel-navigation
php artisan vendor:publish --tag=navigation-config
Open config/navigation.php first — this is the source of truth for menu structure and the place you'll spend most of your time. Check the README for the render entry point (Blade component/directive vs. a Navigation facade call) before writing markup.
First use case: a primary header menu mixing internal routes and at least one external link. In v1.7.0 this is now a first-class concern — set the new external URL flag on the item rather than hardcoding target="_blank" in a template override.
Start small: one menu, three or four items, rendered in layouts/app.blade.php. Verify active-state highlighting and the external item's target/rel output before scaling to footer/sidebar/admin menus.
Config-first, markup-second. Define every menu in config with a stable key (main, footer, account), then render by key. Keeping structure out of Blade means new links are a config diff, not a template edit — the main day-to-day win of this package.
External links as data, not template hacks. With v1.7.0's external URL flag, an item declares its own externality and the package handles the rendering concerns (target="_blank", and ideally rel="noopener noreferrer"). Prefer this over Str::startsWith($url, 'http') checks in your Blade partials — those break on protocol-relative URLs, mailto:, and tel: links. If you already have a custom navigation/item.blade.php override doing string sniffing, delete that branch and pass the flag through.
Separate "is external" from "is active." Use the flag for link-rendering semantics; keep active-state detection driven by route names for internal items. External items should generally never render as active — confirm the package's behaviour and, if it still matches external URLs against the current request, guard it in your own item wrapper.
Programmatic composition for dynamic menus. For menus that depend on runtime state (auth, feature flags, tenant), build or filter the item collection in a service provider or view composer rather than adding conditionals inside config. Config files are cached and cannot evaluate runtime logic.
Cache-friendly structure. Since menus come from config, php artisan config:cache gets you a compiled menu for free. If you're assembling menus at runtime, cache the assembled array with a short TTL and bust it on deploy — nav renders on every page, so it's a hot path.
Testing. Assert rendered output rather than internal structures: $this->get('/')->assertSee('Docs')->assertSee('target="_blank"', false). For the new flag specifically, add a regression test that an external item emits the expected attributes and an internal item does not — this is exactly the kind of attribute-level behaviour that silently regresses on upgrade.
config/navigation.php locally with config:cache active, changes won't appear. php artisan config:clear is the first thing to try when a menu edit "doesn't work."rel attributes. If the package only adds target="_blank", add rel="noopener noreferrer" yourself via an item attribute or a small Blade wrapper. Don't assume the flag covers it — verify in the rendered HTML./en/...) are the usual false negatives for active highlighting. Prefer route-name matching over URL matching wherever the API allows it.can/visible callback, remember these run on every render. Keep them cheap (no queries per item); resolve permissions once and pass a flag in.How can I help you explore Laravel packages today?