saade/filament-adjacency-list
A Filament form component for managing adjacency lists (trees) with drag-and-drop nesting, reordering, and customizable labels, depth limits, item actions, and URLs. Works with arrays or Eloquent relationships.
Architecture fit. This is a single-purpose Filament form component (plus an optional widget) that renders and mutates adjacency-list data — parent/child trees stored as parent_id + sort columns, or resolved through recursive relationships. It is not a domain layer, a repository abstraction, or a data-structure library; it is a presentation-and-interaction layer over a schema you already own. That makes it a strong architectural fit only if the product surface is a Filament panel. For a Laravel API, a Nova/Inertia/Livewire-non-Filament admin, or a customer-facing frontend, the value is near zero — you would be adopting a Blade/Alpine drag-and-drop tree you cannot reuse. The design is composable in the Filament idiom (closures for labels/URLs, action customization, per-field addable/editable/deletable/reorderable/moveable/indentable switches), which means policy enforcement and read-only modes can be expressed declaratively rather than forked.
Integration feasibility. Low effort for a team already on Filament: one composer require, one @source line in the theme CSS, and a model that either exposes a self-referential children() relation or adopts Staudenmeir's HasRecursiveRelationships. The README provides a complete, copy-pasteable relationship recipe (Department → Subjects) and documents the Staudenmeir interop, including the non-obvious getPathSeparator() / getCustomPaths() workaround for teams already using that trait elsewhere. That last detail matters: it signals the maintainer has anticipated coexistence rather than greenfield-only adoption.
Technical risk.
Key questions.
parent_id, sort) with full control, or does it depend on Staudenmeir's package — i.e., are we taking one dependency or two?composer.json PHP constraint, and does it match our runtime floor?HasGraphRelationships, or is a simple parent_id tree sufficient? Graph support materially increases the blast radius of any persistence bug.Stack fit. Filament + Livewire + Alpine, Laravel 12/13, PHP per the package's composer constraint. Ideal target: an internal or back-office panel where menus, category taxonomies, org hierarchies, or ticketing subject trees are edited by a small number of trusted operators. Poor fit for high-concurrency, multi-tenant-editing-the-same-tree scenarios — there is no optimistic locking or conflict resolution in the component.
Migration path.
parent_id (nullable, indexed, FK with nullOnDelete or restrict) and sort (integer, indexed) to the target table. Backfill roots with parent_id = NULL and deterministic sort values. Ship a data-integrity job that asserts no cycles and no orphans before the field is exposed.children() relation with ->with('children')->orderBy('sort') per the README, or adopt Staudenmeir if we already need recursive queries elsewhere. Prefer Staudenmeir only if we have a second use case; otherwise the plain relation keeps the dependency surface at one package.@source directive to the custom theme CSS. This is a hard prerequisite — skipping it yields an unstyled, apparently broken field and a confusing bug report.AdjacencyList on a single resource, in read-only mode first (->addable(false)->editable(false)->deletable(false)->reorderable(false)), to validate rendering against real data volumes.maxDepth() and Laravel policies. Set maxDepth to a number the business actually needs; unlimited depth is a footgun.AdjacencyListWidget only after the form component is stable, and only for read-mostly overview pages.Compatibility. Pin the package major to the Filament major and treat Filament upgrades as a coupled change (package bump + theme rebuild + regression pass on drag/indent/reorder). Maintain a compatibility matrix in the repo: Filament version × Laravel version × package version. The v4.x line's support for Filament 4/5 and Laravel 12/13 in the same window is encouraging but should be verified in staging, not assumed.
Sequencing. Data model and integrity checks → theme/CSS → read-only render → mutation with depth and policy limits → widget. Do not parallelize schema work with UI work; the persistence bugs in the changelog all surface at the seam between the two.
Maintenance. Low steady-state effort: a composer bump per Filament/Laravel cycle, occasional theme rebuilds, and a regression pass on the tree interactions. The real cost is the coupling tax — this package's release cadence is dictated by Filament's, so it lands in our upgrade critical path. Budget one engineer-day per Filament major and a half-day per minor, plus a manual QA script covering add/edit/delete/reorder/indent/outdent/max-depth/policy-denial/save-on-unsaved-owner.
Support. Upstream support is community-only, best-effort, via GitHub issues. There is no SLA, no paid tier, no security disclosure process beyond a policy file. For an internal admin tool that is acceptable; it would not be for a customer-facing editor. We should designate an internal owner, keep a pinned version, and pre-write the fork checklist (branch from tag, vendor the theme views, document the patch points) before we need it.
Scaling.
with('children'); a few hundred nodes is comfortable, low thousands needs measurement. Watch for N+1 patterns if custom itemLabel closures hit the database per node — the README's own example does a Page::find() inside the label closure, which is a per-item query. Cache or preload instead.maxDepth() exists and is enforced on drags and indents (a v4.2.0 fix), but unlimited depth with a recursive relationship invites deep eager-load stacks. Cap depth by policy.Failure modes to instrument.
sort.Ramp-up. A developer familiar with Filament reaches productive use in under an hour — the API is declarative and the README is unusually complete for a package this size (installation caveat, configuration, relationships, Staudenmeir interop, widget, ordering). The learning cost is not the API; it is the operational model: understanding that the field mutates relationships on save, that ordering depends on a column you must supply, and that depth/policy constraints must be configured rather than defaulted. Those are the three items to put in the team's own runbook, because they are the ones the README states once and the changelog shows people getting wrong.
How can I help you explore Laravel packages today?