reedware/nova-treeview
A Laravel Nova package that displays resources in a hierarchical tree view, making it easy to organize, navigate, and manage parent-child relationships and nested records directly within your Nova admin panel.
This is a small, single-purpose Nova extension: it swaps Nova's flat sidebar menu for a collapsible tree. Treat it as a UI patch, not a framework — the whole integration surface is a config file, an asset registration, and one Vue component.
Install and orient yourself
composer require reedware/nova-treeview
php artisan vendor:publish --provider="Reedware\NovaTreeview\TreeviewServiceProvider"
Before writing any code, read these four things in the vendor directory, in this order:
composer.json — the Nova and Laravel version constraints. This single file tells you whether the package can work on your install at all.src/*ServiceProvider.php — where assets get registered (Nova::script/Nova::style vs. a compiled public/ bundle) and whether a config merge happens.config/nova-treeview.php (if published) — the menu/tree structure shape.resources/js/ — the Vue component, so you can see which Nova globals it depends on (Nova.app, Nova.booting, Nova.config('base'), etc.).First use case: nested admin navigation
The canonical job is an app whose Nova sidebar has outgrown a flat list — a CMS with Sections → Categories → Pages, or a multi-tenant tool with per-tenant groups. You want indentation, expand/collapse state, and parent rows that are labels rather than links.
Minimal path to a working tree:
// config/nova-treeview.php (shape will vary — match the published file)
return [
'items' => [
['label' => 'Content', 'children' => [
['label' => 'Pages', 'resource' => 'pages'],
['label' => 'Posts', 'resource' => 'posts'],
]],
['label' => 'Settings', 'children' => [
['label' => 'Users', 'resource' => 'users'],
]],
],
];
Then verify in the browser, not the terminal. php artisan serve won't tell you anything about whether the sidebar rendered — open Nova, hard-refresh (the sidebar is client-side and aggressively cached by the browser), and check the console for Vue warnings. That console is your primary feedback loop for the entire package.
Expectation setting: last release was September 2020, 4 stars, zero dependents. Assume no upstream fixes and plan to vendor or fork the component if you adopt it. Do a 30-minute spike on a throwaway Nova install before proposing it for a production app.
Config-driven tree as the single source of truth
Put the navigation structure in the config file and keep controllers/resources ignorant of it. This lets you diff the menu in code review and lets non-developers read it. If the package's config only supports a flat list with a parent key, normalize it yourself into a nested array in a small helper rather than hand-maintaining indentation levels.
Filter by authorization at boot, not in the component
The Vue layer usually has no idea who the user is beyond what Nova exposes. Prefer computing the visible tree server-side in a NovaServiceProvider::boot() or a dedicated NavigationTree class:
// app/Providers/NovaServiceProvider.php
public function boot()
{
parent::boot();
Nova::script('nova-treeview-auth', __DIR__.'/../../public/js/tree-auth.js');
config(['nova-treeview.items' => app(NavigationTree::class)->forUser(request()->user())]);
}
Guard against the request being null during console commands (request()->user() is null in artisan context) — a common source of "works in browser, explodes in queue worker" bugs.
Blend with, don't fight, Nova's own menu
Nova 3/4 still renders Nova::mainMenu() and any registered tools/cards. Two viable patterns:
Per-environment trees
Because it's config, you get environment variation for free. Use a different tree in local (show all resources, including experimental ones) vs. production (curated). App::environment() checks inside the config file are legitimate here.
Link generation
If items support a url or resource key, generate URLs with Nova's route helpers (Nova::resourcePath('posts'), route('nova.pages...')) rather than hand-written strings. Hardcoded /nova/resources/posts breaks the moment someone changes the Nova path in config/nova.php.
Persist expand/collapse state
Users hate re-expanding the same branch on every page load. If the component doesn't do it, wrap it: store open node keys in localStorage keyed by user ID, and hydrate on mount. This is the single highest-value addition you'll make to the package.
Nova version compatibility is the whole ballgame. A 2020 package targets Nova ~3.x and its compiled asset pipeline. Nova 4 rewrote the front end substantially. Check composer.json constraints against your nova version first; if they don't match, the component will likely fail at Nova.booting with an undefined mixin or a missing Nova.config key. Don't assume --ignore-platform-reqs is a fix.
Assets are pre-compiled. Most packages of this vintage ship a built dist/ and never expect you to run npm. If you fork to patch the Vue component, you own the toolchain: you'll need a matching laravel-nova npm dependency, and npm run prod against a mismatched Nova will produce subtly broken CSS. Budget a day for that, not an hour.
Config caching. config:cache in production will freeze your authorization filter into the cached tree if you compute it at config-load time instead of request time. Keep the filtering in a service provider or middleware that runs per-request, not inside the config file, if the tree varies by user.
Browser cache is the #1 false debugging signal. After editing config or assets, hard-refresh. If the sidebar looks stale, check nova-assets/mix-manifest hashes before you start reading source.
Debugging order when the sidebar is blank or duplicated:
php artisan route:list | grep nova — confirm the Nova path matches what your tree URLs assume.Extension points are thin. Expect no events, no published contracts, no interfaces. The practical extension strategy is: publish the config, then either (a) override the config at runtime, or (b) fork the Vue component and register your own script with a distinct name so both load and yours wins. Prefer (a); resort to (b) only for behavior changes.
CSS specificity fights. A sidebar replacement lives next to Nova's own styles and any theme package. Isolate your overrides under a single wrapper class rather than targeting Nova's generated classes, which change between minor versions.
Have an exit plan. With 0 dependents and no releases since 2020, treat the package as a design reference as much as a dependency. The pattern — nested array config + a Vue component rendering it into the sidebar slot — is ~200 lines to reimplement. If the spike shows friction, copying the idea into your own Nova card/tool is often cheaper than maintaining a dead dependency, and it survives the Nova 5 upgrade.
How can I help you explore Laravel packages today?