saintsystems/nova-resource-group-menu
Organizes Laravel Nova resources into collapsible sidebar groups, making large admin panels easier to navigate. Adds grouped menu sections, configurable labels, and icons so related resources stay tidy without custom tooling.
Version-gate before you write a line of code. The only release is 0.1.0 (2020-05-12), which puts this squarely in the Nova 2.x/3.x era. Check composer.lock for laravel/nova's major first:
Nova::mainMenu() with MenuGroup/MenuSection is native, and Nova 4's Vue 3/Inertia rewrite almost certainly breaks this package's asset layer. Treat the repo as a reference, not a dependency.Spike first, install second. Do this on a throwaway branch:
composer require saintsystems/nova-resource-group-menu:0.1.0
Then read the source before you trust it — the README is effectively empty, so the code is the documentation:
ls vendor/saintsystems/nova-resource-group-menu/src
cat vendor/saintsystems/nova-resource-group-menu/src/*ServiceProvider.php
ls vendor/saintsystems/nova-resource-group-menu/resources
You're answering three questions: (1) does it hook a public Nova API or reach into internals/Vue components, (2) does it ship a config file you need to publish, (3) does it register assets via Nova::script()/Nova::style() that need php artisan nova:publish.
First use case. The package is purely presentational — Nova already groups resources by the static $group property; this turns those rendered headings into collapsible sub-menus. So your smoke test is:
// app/Nova/Invoice.php
public static $group = 'Billing';
// app/Nova/Subscription.php
public static $group = 'Billing';
// app/Nova/Product.php
public static $group = 'Catalog';
Boot the panel, expand Billing, confirm both resources render and the group toggles. Resources visible but headings flat = assets didn't load (publishing problem). Resources gone entirely = real incompatibility; roll back.
Treat $group as the single source of truth. Stop thinking "menu package" and start thinking "renderer for $group." Define groups as constants in one place so labels don't drift:
// app/Nova/Groups.php
final class Groups
{
public const BILLING = 'Billing';
public const CATALOG = 'Catalog';
public const SUPPORT = 'Support';
public const USERS = 'Users & Access';
}
// app/Nova/Invoice.php
public static $group = Groups::BILLING;
This gives you grep-ability and one place to rename a group — which matters because a typo in $group silently creates a new group rather than erroring.
Group by operator domain, not by model. The failure mode of grouping is regrouping into a mirror of your database schema. Group by the job operators are doing — Billing, Catalog, Support, Users & Access — and put Refund under Billing even if it lives in the support schema. If a group has one resource in it, it probably shouldn't be a group.
Day-to-day workflow: adding a resource. Creating a new Nova resource is a two-line ritual — $group = Groups::X, then confirm the group's ordering entry covers it. Add that second check to your PR template; unlisted groups are where ordering surprises come from.
Ordering is a config concern; don't fight it in the menu layer. If the package exposes a config file for labels/icons/ordering, publish it and commit it:
php artisan vendor:publish --provider="Saintsystems\NovaResourceGroupMenu\...ServiceProvider"
Keep the ordering array explicit and complete rather than relying on alphabetical fallback — an unlisted group's position is undefined behaviour you discover in production.
Localize labels through config, not $group. Because $group is a static string, it's awkward to translate. If the package reads labels from config, put __('nova.groups.billing') there and leave $group as a stable key. If it doesn't support that, leave $group as canonical English — don't override $group per-locale at runtime or resources swap groups mid-session.
Combine with Nova's authorization; don't duplicate it. Groups are navigation chrome; policies still decide what renders. Verify that a group whose resources are all hidden by policy disappears on its own. If it renders as an empty collapsible header, decide whether to hide it in config or accept it — don't add a second permission layer in the menu.
Ship a render smoke test. This is the highest-value thing you can do with an unmaintained dependency:
// tests/Feature/NovaMenuTest.php
public function test_grouped_resources_render(): void
{
$this->actingAs(User::factory()->create(['is_admin' => true]))
->get('/nova')
->assertOk()
->assertSee('Billing')
->assertSee('Catalog');
}
Crude, but it catches the two failure modes that actually bite: a boot-time exception that takes the whole admin panel down, and a silent flattening after an upgrade.
Keep the reimplementation path warm. On Nova 4+, the equivalent is roughly:
// NovaServiceProvider::boot()
Nova::mainMenu(function (Request $request) {
return [
MenuSection::make('Billing', [
MenuItem::resource(Invoice::class),
MenuItem::resource(Subscription::class),
])->icon('credit-card'),
];
});
About a hundred lines, in a file you already own. If you're going to fork this anyway, seriously evaluate whether landing that in NovaServiceProvider is cheaper.
Zero dependents means you are the QA function. 27 stars, no downstream installs, no release in ~5 years, no visible CI matrix. Nobody else has incidentally compatibility-tested this against your Nova/PHP/Laravel triple. Budget the half-day spike as mandatory.
The dangerous failure is silent, not loud. A boot-time exception is obvious — admin panel down, you see it instantly. The nasty one is a Nova minor upgrade that leaves the menu rendering while groups flatten or resources drop out; that surfaces as an operator ticket six weeks later. Pin exactly ("saintsystems/nova-resource-group-menu": "0.1.0"), not with a caret, and lean on the render test.
Expect PHP 8.x noise. A 2020 codebase on PHP 8.1+ will typically emit deprecation notices (dynamic properties, implicit nullable params). Run the spike with APP_DEBUG=true and your error log open. Deprecations escalate to hard failures on a PHP minor bump — decide now whether you're forking to fix them.
Asset publishing is the usual "it installed but does nothing" cause. Config loads, $group values are right, headings stay flat → check public/vendor/nova and re-run php artisan nova:publish. Nova caches compiled assets aggressively; clear it between attempts.
Conflicts with other navigation customizations are painful to bisect. Two packages mutating the same menu produce ordering and loss bugs that look like either package misbehaving. If you already have a custom menu, a theme, or another sidebar tool, install this last and test with the others disabled first.
Doubled headings mean the asset layer isn't taking effect. Nova already renders <h3> group headers for $group. If the package's JS doesn't fully suppress them you get a heading inside a heading — same diagnosis as the publishing problem above, not a grouping problem.
Pin the decision, not just the version. MIT makes "buy" free, so this is an ownership decision, not a licensing one. Either vendor the source into your repo with a named owner and a recurring allowance against Nova upgrades, or reimplement natively. A floating Composer dependency with no owner is the one outcome with no good ending.
The package is a better tutorial than a dependency. Read it once — it's small enough to finish in under an hour — and use it to learn where your Nova version's sidebar hooks live. Then write your own. That's the extension point worth internalizing here.
How can I help you explore Laravel packages today?