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.
Architecture fit. This is a presentation-layer package, not a platform component. It does not own data, expose APIs, or introduce new domain concepts — it reads the static $group property that Nova already renders on each Resource and converts the resulting flat headings into labelled, collapsible sub-menus. From a TPM's chair that is the important framing: the blast radius is confined to the admin sidebar's information architecture, and the underlying resource registry, routing, authorization, and policies are untouched. That makes it cheap to trial and cheap to abandon. It also means the capability is not differentiated — Nova 4+ ships Nova::mainMenu() with MenuGroup / MenuSection and collapsible groups natively, which turns this package into a reference implementation rather than a strategic dependency.
Integration feasibility. Feasible but entirely version-gated. The last release is 0.1.0 (2020-05-12), placing it in the Nova 2.x/3.x era. On Nova 4/5 the Vue 3 / Inertia rewrite almost certainly breaks the asset layer, so the gate is a one-line check of laravel/nova in composer.lock before any engineering time is committed. On Nova 2/3 the integration is a composer require, an optional vendor:publish, a nova:publish for assets, and assigning $group constants across Resource classes. The README is effectively empty, so the source is the documentation — budget an hour to read it and confirm whether it hooks a public Nova API or reaches into internals, since that determines how brittle the integration is against patch-level Nova bumps.
Technical risk. The risk profile is asymmetric and dominated by ownership, not licensing. MIT means "buy" is free, so the real decision is build vs. borrow. With 27 stars, zero dependents, and no release in roughly five years, we are the QA function — nobody else has incidentally compatibility-tested this against our Nova/PHP/Laravel triple. The dangerous failure is silent rather than loud: a boot-time exception takes the panel down and is obvious, but a Nova minor upgrade that leaves the menu rendering while groups flatten or resources drop out surfaces as an operator filing "the sidebar looks wrong" six weeks later. Secondary risks are routine for a 2020 codebase on PHP 8.1+ — dynamic-property and implicit-nullable deprecations that escalate to hard failures on a PHP minor bump — and conflicts with any existing custom menu, theme, or sidebar tool, which are painful to bisect because two packages mutating the same menu look like either one misbehaving.
Key questions.
NovaServiceProvider, a file we already own?Stack fit. Correct fit only for Nova 2/3 shops with an established Laravel codebase and an existing PHP owner. It composes cleanly with Nova's authorization model — groups are navigation chrome, while Authorizable and policies still decide what renders — so there is no second permission layer to design or defend. The recommended pattern treats $group as the single source of truth and centralizes labels as constants in one class (app/Nova/Groups.php) so group names are greppable and renameable in one place; a typo in $group silently creates a new group rather than erroring, which is exactly the kind of defect that reaches production unnoticed.
Migration path. Two distinct paths, and the TPM should pick one deliberately rather than defaulting.
NovaServiceProvider::boot() via Nova::mainMenu() returning MenuSection::make(...) collections — roughly a hundred lines, a file we already own, with our name on the git blame. On Nova 4/5 this is not a migration, it is simply the only supported path, and the package becomes a tutorial we read once in under an hour to learn where the sidebar hooks live in our version.Compatibility. Pin exactly ("saintsystems/nova-resource-group-menu": "0.1.0"), never with a caret. Run any spike with APP_DEBUG=true and the error log open to catch deprecation noise before it becomes a failure. Asset publishing is the usual cause of "it installed but does nothing": if config loads and $group values are correct but headings stay flat — or you see a heading nested inside a heading from Nova's own <h3> — check public/vendor/nova and re-run php artisan nova:publish, clearing Nova's aggressively cached compiled assets between attempts. If resources vanish entirely rather than flattening, that is a genuine incompatibility; roll back.
Sequencing.
composer.lock for laravel/nova. Nova 4/5 stops here — use the native API.src/ and resources/ before trusting anything.Groups constants and group by operator domain (Billing, Catalog, Support, Users & Access) — not by model or database schema. A group with one resource in it probably shouldn't be a group.Instrumentation to build alongside it. If the goal is evidence rather than aesthetics, pair the prototype with something measurable: task time to locate a resource, support tickets phrased "where is X," and an operator survey score. That converts a vague complaint into an artifact suitable for prioritization, and it is the deliverable that outlives the package regardless of which path you take.
Maintenance. This is the dominant operational cost and it is entirely ours. No release in ~5 years, no issue triage, no compatibility guarantee, no community safety net. A 2020 PHP codebase will emit deprecations on 8.1+ that escalate to hard failures on a PHP minor bump, so decide during the spike whether we are forking to fix them. Maintenance effort is low in absolute terms — the package is small enough to read in under an hour — but it is unbounded in duration unless we vendor the source into our repo with a named owner and a recurring allowance against Nova upgrades.
Support. There is no vendor to call. Internal operators will route sidebar complaints to whichever engineer owns Nova, and that engineer will be debugging a dependency nobody on the team wrote. The realistic support posture is: name an owner before install, define the escalation path (roll back to the pinned version, reimplement natively), and make clear that "the sidebar looks wrong" is a P2 with a known diagnosis path rather than a novel incident each time.
Scaling. The package scales fine on the axis that matters here — it is configuration-only, so adding the 30th or 40th resource costs a $group constant and a config ordering entry, no engineering. It scales badly on two other axes. First, group proliferation: if every team adds a group, the sidebar becomes a hierarchy that is navigable in principle and useless in practice; enforce a small, stable taxonomy by operator domain and prune single-resource groups. Second, localization: $group is a static string on a Resource, so translating labels is awkward. If the package reads labels from config, put __('nova.groups.billing') there and leave $group as a stable key; if it does not, accept canonical English and do not override $group per locale at runtime — that path leads to resources swapping groups mid-session.
Failure modes. Ranked by how much they cost us:
| Mode | Signal | Cost | Mitigation |
|---|---|---|---|
| Silent regression after Nova/PHP bump | Menu renders but groups flatten or resources drop out | High — surfaces weeks later as an operator complaint | Exact version pin + render smoke test (assertSee('Billing')) in CI |
| Asset layer not loaded | Resources render, headings stay flat, or doubled labels | Low — cosmetic, obvious, one command to fix | nova:publish, clear Nova's compiled-asset cache |
| Boot-time exception | Admin panel down | Medium — loud but total | Spike on a throwaway branch first; never install directly on main |
| PHP 8.x deprecation escalation | Warnings in log, later hard failures | Medium — silent until a minor bump | APP_DEBUG=true during spike; fork or pin PHP minor |
| Conflict with another menu/theme package | Ordering loss, resources disappearing | High — painful to bisect | Install last, test with others disabled first |
| Empty group header from policy filtering | Collapsible header with nothing under it | Low — cosmetic, but erodes trust in the nav | Verify a fully-hidden group disappears on its own; otherwise hide via config, don't add a second permission layer |
The smoke test is the highest-value artifact here and it is deliberately crude:
public function test_grouped_resources_render(): void
{
$this->actingAs(User::factory()->create(['is_admin' => true]))
->get('/nova')
->assertOk()
->assertSee('Billing')
->assertSee('Catalog');
}
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.
Ramp-up. Low for anyone already fluent in Nova. Reading the source plus the spike is half a day, mandatory rather than optional given zero dependents — we are the QA function. The native reimplementation on Nova 4+ is a day or two for a Laravel engineer familiar with NovaServiceProvider, and it eliminates the maintenance tail entirely, which makes it the cheaper long-term path on almost every timeline that extends past one quarter. Where ramp-up is genuinely expensive is debugging: bisecting menu conflicts and diagnosing flat-versus-vanished headings both require reading Nova's menu internals, so treat that as a named skill on the owning engineer rather than assumed knowledge.
Bottom line for the roadmap. The opportunity signal is low — this is convenience, not differentiation, and on Nova 4+ it is already native. Recommend against adopting it as a standing dependency. If sidebar IA is a validated operator pain point, run it as a short prototype to generate evidence, then land ~100 lines of Nova::mainMenu() that we own. If it is not a validated pain point, the correct action is to trim what the sidebar exposes rather than nest what it already has — cheaper, faster, and nobody has to own anything.
How can I help you explore Laravel packages today?