wsmallnews/filament-nestedset
Filament nestedset tree plugin built on kalnoy/nestedset, with multi-language and multi-tenancy support, unlimited nesting levels, tabbed switching, and a parent select field for elegant tree management.
Build vs. buy for tree-shaped content. Most admin panels eventually need a hierarchy: nav menus, CMS categories, product taxonomies, org/department structures, knowledge-base sections, file or asset folders, location regions. The realistic in-house version of this (drag-and-drop reorder + reparent, nested CRUD forms, level limits, safe deletion rules, tenant scoping) is roughly one to three engineer-weeks plus ongoing polish. This package is a page class plus a composer require, so the buy case is strong if you are already on Filament v4/v5 and a nested-set model.
Feature scoping. The README effectively doubles as a scope checklist for the tree epic. Decisions you can make and defend:
$level / getLevel() — lets you ship a taxonomy without committing to a hard IA limit, then tighten later once content editors reveal actual depth.allow_delete_parent = false, allow_delete_root = false. This makes "you can't delete a category with children" the default UX rather than a bug; you consciously choose cascade or reparent instead.create_action_modal_show_parent_select and per-row "create child" action — decides whether users add nodes top-down from a modal or in-place from the row they're looking at.infolistSchema() (status, count, owner) with breakpoint and alignment controls — turns the tree into a scannable operational view rather than a bare list.getRecordLabel() returning HtmlString — supports badges, status dots, translated labels in the node title without forking the view.Multi-tenant roadmap enablement. Tenant-scoped trees are supported through kalnoy/nestedset's scoping, driven by getScopeAttributes() on the model plus $isScopedToTenant = false for global pages. This is the single biggest planning input: if your panel is multi-tenant, every tree table needs the tenant (and any scope) column in its schema from day one. Retrofitting scoping columns onto existing tree data is a migration, not a config flip — so decide tenancy before the first tree ships.
Tabs as a segmentation pattern, not a localization feature. $tabFieldName + getTabs() gives you multiple trees on one page (e.g. Website Navigation / Shop Navigation) by appending the tab value to the nested-set scope. Product implication: tabs are a data partition, so the tab field must be in getScopeAttributes(). Good for multi-channel or multi-type taxonomies; a poor fit if you want tabs that are filtered views over one unpartitioned tree.
Integration surface for adjacent roadmap items. The Livewire tree component emits sn-filament-nestedset-leaf-click / node-click events and exposes getRecordUrl for custom navigation, plus getEloquentQuery() for status/visibility filtering. That means "click a category to open its articles/products" and "only show published nodes" are cheap follow-on stories rather than new builds.
Discovery item to resolve early: the "multi-language" claim is UI string localization (publishable translations). It does not appear to mean per-locale node content. If your roadmap needs translated category names per locale, that is a schema and form-schema decision on your side — scope it explicitly, don't inherit it from the package description.
Adopt when all of these are true:
v1 branch — treat that as a maintenance risk, not a free option.)kalnoy/nestedset (NodeTrait plus $table->nestedSet()); this package is a UI layer, not a data-structure choice.Look elsewhere, or build, when:
kalnoy/nestedset directly with your own UI, or 15web/filament-tree.Adoption checklist before you commit: pin the constraint (^2.0), publish config/views/translations so behavior is reviewable in-repo, wrap NestedsetPage in a thin internal base class so you can swap implementations, and add an integration test on your model covering reorder, reparent, tenant scope, and blocked parent deletion. Verify the v1/v3 branch status if any panel is still on v3. Note the Laravel Boost integration (php artisan boost:update --discover) as a genuine onboarding nicety for AI-assisted work.
To executives — one paragraph: We need tree-shaped admin screens for [menus / categories / org units], and the honest in-house cost is one to three engineer-weeks per screen family plus indefinite upkeep of drag-and-drop edge cases. wsmallnews/filament-nestedset is an MIT-licensed Filament plugin that delivers that UI — drag-and-drop reordering, parent selection, depth limits, safe-delete rules, tenant-scoped trees, and tabbed datasets — as a page class we configure in an afternoon. It sits on kalnoy/nestedset, a widely used Laravel library, so our data model and data are portable and the lock-in is low. The trade-off is maturity: small community, one maintainer, no support contract. Mitigation is cheap — pin the version, wrap it in one internal base class, and keep our own tests on reorder and tenancy. Recommendation: adopt for the tree screens this quarter, with a deliberate exit path.
To engineering — the framing: It is a thin, well-behaved UI layer over kalnoy/nestedset, not a new persistence model. You keep NodeTrait, the nestedSet() migration columns, and your scoping semantics; you gain the Filament page, tree component, create/edit modal schemas, and parent-select field (codewithdennis/filament-select-tree). Extension points are conventional and override-friendly: schema() / createSchema() / editSchema(), getRecordLabel() (accepts HtmlString), infolistSchema() plus $infolistHiddenEndpoint / $infolistAlignment, getEloquentQuery(), getLevel(), nestedScoped(), $isScopedToTenant, $tabFieldName with getTabs(), and $emptyLabel / $emptyTipLabel. The Livewire tree component emits sn-filament-nestedset-leaf-click and node-click events and exposes getRecordUrl for navigation, so click-through behaviors are additive. Non-negotiables on our side: tenant and tab fields must be added to getScopeAttributes() before any data exists; config defaults block deleting parents and roots, so cascade/reparent behavior is an explicit decision; and we pin ^2.0, publish config and views into the repo, and add regression tests on reorder, reparent, scoped queries, and delete guards before this reaches a customer-facing screen.
How can I help you explore Laravel packages today?