offload-project / laravel-navigation
Configurable navigation package for Laravel with breadcrumb and tree generation
Installs: 323
Dependents: 1
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/offload-project/laravel-navigation
Requires
- php: ^8.3
- enshrined/svg-sanitize: ^0.22
- illuminate/routing: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- captainhook/captainhook-phar: ^5.27
- larastan/larastan: ^3.8.1
- laravel/pint: ^1.26.0
- laravel/wayfinder: ^0.1.12
- orchestra/testbench: ^9.15|^10.8
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- ramsey/conventional-commits: ^1.6
- spatie/laravel-data: ^4.18
This package is auto-updated.
Last update: 2026-01-11 01:07:57 UTC
README
Laravel Navigation
A powerful, flexible navigation management package for Laravel. Define multiple navigation structures with breadcrumbs, active state detection, and pre-compiled icons — perfect for Inertia.js, React, Vue, and Blade applications.
Features
- Multiple Navigations — Define unlimited nav structures (main, footer, sidebar, user menu)
- Fluent Builder API — IDE-friendly builder with full autocomplete support
- Route-Based — Use Laravel route names with full IDE autocomplete
- Breadcrumb Generation — Auto-generate breadcrumbs from your navigation config
- Active State Detection — Smart detection of active items and their parents
- Authorization — Built-in
canandvisibleattributes for permissions - Pre-compiled Icons — Compile Lucide icons to inline SVG for optimal performance
- Action Support — POST/DELETE actions for logout, form submissions, etc.
- Wildcard Parameters — Match dynamic routes like
/users/{id}/editin breadcrumbs - Custom Metadata — Attach badges, feature flags, or any data to nav items
Requirements
- PHP 8.3+
- Laravel 11.0+
Installation
composer require offload-project/laravel-navigation
Optionally publish the configuration:
php artisan vendor:publish --tag=navigation-config
Quick Start
Using the Fluent Builder (Recommended)
The fluent builder provides full IDE autocomplete and a clean, readable syntax:
use OffloadProject\Navigation\Item; return [ 'navigations' => [ 'main' => [ Item::make('Dashboard')->route('dashboard')->icon('home'), Item::make('Users') ->route('users.index') ->icon('users') ->can('view-users') ->children([ Item::make('All Users')->route('users.index'), Item::make('Roles')->route('roles.index'), ]), Item::make('Settings')->route('settings')->icon('settings'), ], ], ];
Using Helper Functions
Global helper functions provide the most concise syntax:
return [ 'navigations' => [ 'main' => [ nav_item('Dashboard', 'dashboard', 'home'), nav_item('Users', 'users.index', 'users') ->can('view-users') ->children([ nav_item('All Users', 'users.index'), nav_item('Roles', 'roles.index'), ]), nav_separator(), nav_item('Settings', 'settings', 'settings'), nav_external('Documentation', 'https://docs.example.com', 'book'), nav_action('Logout', 'logout', 'post', 'log-out'), ], ], ];
Available helpers:
nav_item($label, $route?, $icon?)— Standard navigation itemnav_group($label, $children?, $icon?)— Collapsible group/sectionnav_separator()— Visual separatornav_divider($spacing?)— Divider with optional spacingnav_external($label, $url, $icon?)— External linknav_action($label, $route, $method?, $icon?)— POST/DELETE action
Using Shorthand Syntax
For quick configuration, use the shorthand array syntax:
return [ 'navigations' => [ 'main' => [ ['Dashboard', 'dashboard', 'home'], // [label, route, icon] ['Users', 'users.index', 'users'], // [label, route, icon] ['Docs', 'https://docs.example.com', 'book'], // URLs auto-detected ], ], ];
Using Array Syntax
The traditional array syntax is still fully supported:
return [ 'navigations' => [ 'main' => [ ['label' => 'Dashboard', 'route' => 'dashboard', 'icon' => 'home'], [ 'label' => 'Users', 'route' => 'users.index', 'icon' => 'users', 'can' => 'view-users', 'children' => [ ['label' => 'All Users', 'route' => 'users.index'], ['label' => 'Roles', 'route' => 'roles.index'], ], ], ], ], ];
Runtime Registration
Register navigations at runtime in your service provider or middleware:
use OffloadProject\Navigation\Facades\Navigation; use OffloadProject\Navigation\Item; // Fluent builder Navigation::register('sidebar') ->item('Dashboard', 'dashboard', 'home') ->item('Users', 'users.index', 'users') ->child('All Users', 'users.index') ->child('Create User', 'users.create') ->separator() ->item('Settings', 'settings', 'settings') ->done(); // Or with Item instances Navigation::addNavigation('sidebar', [ Item::make('Dashboard')->route('dashboard')->icon('home'), Item::make('Users')->route('users.index')->icon('users'), ]);
Getting Navigation Data
use OffloadProject\Navigation\Facades\Navigation; // Get navigation items $items = Navigation::get('main')->items(); // Get breadcrumbs (auto-detects current route) $breadcrumbs = Navigation::breadcrumbs('main'); // Check if navigation exists if (Navigation::has('sidebar')) { // ... } // Get all navigation names $names = Navigation::names();
Pass to your frontend:
// Inertia.js return inertia('Dashboard', [ 'navigation' => Navigation::get('main')->items(), 'breadcrumbs' => Navigation::breadcrumbs('main'), ]);
Fluent Builder API
The Item class provides a fluent interface with full IDE support:
use OffloadProject\Navigation\Item; // Basic item Item::make('Dashboard')->route('dashboard')->icon('home') // Shorthand with route and icon Item::to('Dashboard', 'dashboard', 'home') // External link Item::external('Documentation', 'https://docs.example.com', 'book') // Action (POST/DELETE) Item::action('Logout', 'logout', 'post', 'log-out') // Separator Item::separator() // Divider with spacing Item::divider('large') // With children Item::make('Settings') ->route('settings') ->icon('settings') ->children([ Item::make('Profile')->route('settings.profile'), Item::make('Security')->route('settings.security'), ]) // With authorization Item::make('Admin') ->route('admin') ->can('access-admin') // With visibility Item::make('Dashboard') ->route('dashboard') ->visible(fn () => auth()->check()) ->whenAuthenticated() // Shorthand for authenticated users ->whenGuest() // Shorthand for guests only // With badge Item::make('Notifications') ->route('notifications') ->badge(5) ->badge(fn () => auth()->user()->unreadCount(), 'red') // For breadcrumbs only Item::make(fn ($user) => "Edit: {$user->name}") ->route('users.edit') ->params(['user' => '*']) ->breadcrumbOnly() // For navigation only Item::make('Admin Section') ->route('admin') ->navOnly() // Custom metadata Item::make('Dashboard') ->route('dashboard') ->meta('badge', 5) ->meta('feature', 'beta')
Groups & Sections
Organize navigation items into collapsible groups with headers:
use OffloadProject\Navigation\Item; return [ 'navigations' => [ 'sidebar' => [ Item::make('Dashboard')->route('dashboard')->icon('home'), Item::group('Settings', [ Item::make('Profile')->route('settings.profile'), Item::make('Security')->route('settings.security'), Item::make('Notifications')->route('settings.notifications'), ])->icon('cog'), Item::group('Administration', [ Item::make('Users')->route('admin.users'), Item::make('Roles')->route('admin.roles'), ])->icon('shield')->collapsed(), // Starts collapsed ], ], ];
Or with helper functions:
return [ 'navigations' => [ 'sidebar' => [ nav_item('Dashboard', 'dashboard', 'home'), nav_group('Settings', [ nav_item('Profile', 'settings.profile'), nav_item('Security', 'settings.security'), ], 'cog'), nav_group('Admin', [], 'shield') ->collapsed() ->can('access-admin') ->children([ nav_item('Users', 'admin.users'), nav_item('Roles', 'admin.roles'), ]), ], ], ];
Group Options
// Basic group Item::group('Settings', [...]) // With icon Item::group('Settings', [...])->icon('cog') // Not collapsible (always expanded) Item::group('Main')->collapsible(false) // Starts collapsed Item::group('Advanced')->collapsed() // With authorization Item::group('Admin')->can('access-admin') // With visibility Item::group('Beta')->visible(config('features.beta')) // Nested groups Item::group('Settings', [ Item::make('General')->route('settings.general'), Item::group('Advanced', [ Item::make('API Keys')->route('settings.api'), Item::make('Webhooks')->route('settings.webhooks'), ])->collapsed(), ])
Group Output
Groups output with these additional fields:
[
'id' => 'nav-sidebar-1',
'label' => 'Settings',
'url' => null, // Groups don't have URLs
'isActive' => true, // Active if any child is active
'icon' => '<svg>...</svg>',
'group' => true, // Identifies this as a group
'collapsible' => true, // Can be collapsed
'collapsed' => false, // Default collapsed state
'children' => [...],
]
Route Parameters
Pass parameters for routes that require them:
// For routes like /users/{user}/posts $items = Navigation::get('sidebar')->items(['user' => $user->id]);
Authorization
Use the can attribute to check gates or policies:
Item::make('Admin') ->route('admin.index') ->can('access-admin') // With policy model Item::make('Edit Post') ->route('posts.edit') ->can(['update', $post])
Items are automatically hidden when the user isn't authenticated or lacks permission.
For non-authorization logic (feature flags, environment checks), use visible:
Item::make('Beta Features') ->route('beta') ->visible(config('features.beta')) // Or with closures Item::make('Debug') ->route('debug') ->visible(fn () => app()->isLocal())
Action Items
Define items that trigger POST/DELETE requests:
Item::action('Logout', 'logout', 'post', 'log-out') // Or with array syntax ['label' => 'Logout', 'route' => 'logout', 'method' => 'post', 'icon' => 'log-out']
Handle in your frontend by checking for the method key and using a form or Inertia's router.post().
Breadcrumbs & Wildcards
Handle CRUD pages elegantly with breadcrumbOnly and wildcard parameters:
Item::make('Users') ->route('users.index') ->children([ Item::make(fn ($user) => "Edit: {$user->name}") ->route('users.edit') ->params(['user' => '*']) ->breadcrumbOnly(), ])
When visiting /users/5/edit:
- Navigation shows only "Users"
- Breadcrumbs show "Users > Edit: John Doe"
- Active state marks "Users" as active
Breadcrumbs API
// Auto-detect current route, search all navigations $breadcrumbs = Navigation::breadcrumbs(); // Search specific navigation $breadcrumbs = Navigation::breadcrumbs('main'); // Specify route explicitly $breadcrumbs = Navigation::breadcrumbs('main', 'users.edit'); // With route parameters $breadcrumbs = Navigation::breadcrumbs('main', 'users.edit', ['user' => $user]);
Visibility Control
Use navOnly and breadcrumbOnly to control where items appear:
Item::make('Admin Section') ->route('admin.index') ->navOnly() // Shows in nav, excluded from breadcrumbs ->children([ Item::make('Users')->route('admin.users'), Item::make(fn ($user) => "Edit {$user->name}") ->route('admin.users.edit') ->params(['user' => '*']) ->breadcrumbOnly(), // Shows in breadcrumbs, excluded from nav ])
navOnly— Section headers that would be redundant in breadcrumbsbreadcrumbOnly— Edit/show pages that shouldn't clutter navigation
Custom Metadata
Attach any data to navigation items:
Item::make('Notifications') ->route('notifications') ->badge(5) ->meta('badgeColor', 'red') ->meta('feature', 'new') // Or with array syntax [ 'label' => 'Notifications', 'route' => 'notifications', 'badge' => 5, 'badgeColor' => 'red', ]
Custom keys pass through unchanged to your frontend.
Icon Compilation
Pre-compile Lucide icons to inline SVG for optimal performance:
php artisan navigation:compile-icons
Add to your deployment pipeline for production builds.
Route Validation
Validate all route references exist:
php artisan navigation:validate
Add to CI/CD to catch broken navigation links early.
Inertia.js Integration
Share navigation globally via middleware:
// app/Http/Middleware/HandleInertiaRequests.php public function share(Request $request): array { return [ ...parent::share($request), 'navigation' => Navigation::get('main')->items(), 'breadcrumbs' => Navigation::breadcrumbs('main'), ]; }
Output Format
The items() method returns a frontend-ready structure:
[
[
'id' => 'nav-main-0',
'label' => 'Dashboard',
'url' => '/dashboard',
'isActive' => true,
'icon' => '<svg>...</svg>',
'children' => [],
],
// ...
]
Configuration Reference
| Option | Type | Description |
|---|---|---|
label |
string|Closure |
Display text (closures receive route models) |
route |
string |
Laravel route name |
url |
string |
External URL (alternative to route) |
method |
string |
HTTP method (post, delete) |
icon |
string |
Lucide icon name |
children |
array |
Nested navigation items |
visible |
bool|Closure |
Visibility condition |
can |
string|array |
Gate/policy check |
breadcrumbOnly |
bool |
Hide from nav, show in breadcrumbs |
navOnly |
bool |
Show in nav, hide from breadcrumbs |
params |
array |
Route parameters (['id' => '*'] for wildcards) |
Error Messages
The package provides helpful error messages when configuration is invalid:
Navigation item cannot have both "route" and "url". Use "route" for internal
Laravel routes (e.g., "users.index") or "url" for external links
(e.g., "https://docs.example.com"), but not both.
See: https://github.com/offload-project/laravel-navigation#routing
Migration Guide
Upgrading to v1.1
Breaking Changes
Icon Storage Format Changed
Compiled icons are now stored as JSON instead of PHP. If you have previously compiled icons:
# Recompile your icons to use the new format
php artisan navigation:compile-icons
The old PHP format (storage/navigation/icons.php) will still be loaded for backwards compatibility, but new compilations will use JSON (storage/navigation/icons.json).
NavigationManager Constructor
If you're manually instantiating NavigationManager, the constructor now requires an ItemVisibilityResolver instance:
// Before new NavigationManager($config, $iconCompiler); // After new NavigationManager($config, $iconCompiler, $visibilityResolver);
Most users won't be affected as the class is typically resolved from the container.
Deprecated Methods
The following methods are deprecated and will be removed in v2.0:
| Deprecated | Use Instead |
|---|---|
->toTree() |
->items() |
->getBreadcrumbs() |
->breadcrumbs() |
Testing
./vendor/bin/pest
License
The MIT License (MIT). Please see License File for more information.