Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Nova Order Nestedset Field Laravel Package

novius/laravel-nova-order-nestedset-field

A Laravel Nova field for ordering nested set models. Drag and drop tree nodes, move items between parents, and reorder siblings, backed by nested set logic. Configurable depth, sibling or free ordering, and smooth integration with Nova resource forms and index views.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify the three prerequisites before running composer require. This is a Nova field, not a standalone package. It does nothing outside the Nova admin panel, and it does nothing without a nested-set model underneath it.

  1. Laravel Nova installed and working. Nova is commercial and its field API shifts between majors. Note your exact Nova major before you look at this package's composer.json constraint.
  2. A model using kalnoy/nestedset (NodeTrait) with _lft, _rgt, parent_id, and usually depth.
  3. A Nova resource pointing at that model.

If the real requirement is "let an admin reorder twelve flat rows," stop here and use spatie/laravel-sortable with an integer column. Nested set is disproportionate machinery for flat ordering, and its failure modes are silent rather than loud.

composer require novius/laravel-nova-order-nestedset-field

Model first, field second. The field binds to nested-set columns and will not work on a plain Eloquent model.

use Kalnoy\Nestedset\NodeTrait;

class Category extends Model
{
    use NodeTrait;

    protected $fillable = ['name', 'parent_id'];
}

No columns yet? Add them with the blueprint macro, then backfill into a valid tree before the field ever renders:

Schema::table('categories', function (Blueprint $table) {
    $table->nestedSet(); // _lft, _rgt, parent_id + indexes
});
// after populating parent_id values
Category::fixTree();

A half-backfilled tree is the classic first-day failure: the tree renders, drags appear to save, and the ordering is quietly wrong afterwards.

Register the field on the resource.

public function fields(Request $request)
{
    return [
        // ... other fields
        NestedSetOrderField::make('Hierarchy')
            ->withMeta([/* display config — see README */]),
    ];
}

The exact meta keys for which attributes render per node and which behaviours are enabled live in the README and src/. The surface area is small; read the PHP field class and the Vue component end to end in one sitting. Given the adoption signal (23 stars, zero dependents), that hour is the cheapest diligence you'll do.

Where to look first:

  • README.md — install snippet and field options.
  • src/ — the PHP field class; this is the file you'd fork.
  • dist/js/ (or resources/js/) — compiled Vue component; drag handling lives here.
  • composer.json — read the Nova constraint and the kalnoy/nestedset constraint line by line before committing.

First use case. Pick one genuinely hierarchical, low-stakes resource — a nav menu or a product category tree — on a branch. Migrate, backfill, fixTree(), add the field, then exercise the four operations that matter: reorder within a parent, move to a new parent, delete a node with children, and create a child at depth > 2. Only roll out to a second resource once that survives a sprint.

Implementation Patterns

Keep the tree field as an index-view concern; keep a plain editing path alongside it. Pair it with a normal Text or BelongsTo input for parent_id on the detail view. Power users get a non-drag fallback when the UI misbehaves, and you get a diagnostic lever: if the text field writes correctly and the drag doesn't, the bug is in the field, not the model.

Subclass the field on day one. Cheapest available insurance, minutes of work:

// app/Nova/Fields/OrderedTree.php
class OrderedTree extends \Novius\NovaOrderNestedSetField\Field
{
    // your defaults: display fields, labels, meta
}

Resources reference OrderedTree, never the vendor class. Swapping to a fork or a rewrite later becomes a one-file change instead of a find-and-replace across every resource.

One rule for the whole team: all tree writes go through the nested-set API. appendToNode, saveAsRoot, moveTo…, or this field's UI. Seeders, Model::query()->update(), bulk deletes, and import scripts are the number-one source of corrupted trees. If you have importers, write a helper that builds via NodeTrait and finish with fixTree().

Wrap every custom multi-node operation in a transaction. A nested-set move rewrites _lft/_rgt ranges across many rows. A single drag is the package's problem; a bulk reparent or reorder endpoint you write on top is yours:

DB::transaction(function () use ($nodes, $parent) {
    foreach ($nodes as $node) {
        $node->appendToNode($parent)->save();
    }
});

Ship a repair command and a monitoring check early. This is the pattern that separates a working install from a slow-motion incident:

class RepairTrees extends Command
{
    protected $signature = 'trees:repair {model?}';

    public function handle()
    {
        foreach ([Category::class, MenuItem::class] as $model) {
            $errors = (new $model)->countErrors(); // kalnoy/nestedset
            $this->line(class_basename($model).": {$errors} errors");
            if ($errors) {
                (new $model)->fixTree();
                $this->warn('  repaired');
            }
        }
    }
}

Schedule it nightly and log loudly when countErrors() > 0 — a nonzero count after a quiet day means someone is writing to the tree outside the package. Also worth asserting: every node's _rgt - _lft is odd, and no orphaned parent_id values exist.

Test the invariants against your models, not the package's. With zero dependents, you are the QA. A feature suite covering reorder, reparent, subtree delete, and root creation is half a day and is what lets you survive a Nova upgrade:

it('moves a node without corrupting the tree', function () {
    $root = Category::create(['name' => 'Root']);
    $a = Category::create(['name' => 'A']);
    $a->appendToNode($root)->save();

    $b = Category::create(['name' => 'B']);
    $b->appendToNode($a)->save();

    expect($root->refresh()->countErrors())->toBe(0);
    expect($a->children)->toHaveCount(1);
});

Pin the package and Nova independently, and never upgrade both in the same window. When a Nova bump and a package update land together and drags silently stop persisting, you have no bisect point. Upgrade one, re-run the invariant tests, then the other.

Fork or vendor before the second resource. The codebase is small and tractable; the cost of waiting until it breaks exceeds the cost of forking now.

"repositories": [
  { "type": "vcs", "url": "[email protected]:yourorg/nova-ordered-tree.git" }
]

Decide scope per tree, explicitly. The field handles one hierarchy well. Per-tenant trees, per-user ordering, draft/published orderings, and move-history audits are all not supported. Scoped roots are possible in nested set (_lft/_rgt per tenant) but must be verified against this field's queries — and at that point you own the tree logic anyway.

Roll out one resource at a time. Lowest-stakes hierarchy first, watch a sprint, then generalize. This isn't a platform-wide dependency to introduce on day one.

Gotchas and Tips

Silent breakage after a Nova major upgrade is the worst failure mode — worse than a hard error. Nova offers no stable third-party field ABI. A field can mount on Nova 5, look fine, and persist nothing. Always verify a drag wrote to the database, not that the tree redrew. Put a smoke test in your upgrade checklist: load the resource, drag a node, hard refresh, assert the new position survived.

Partially backfilled trees. fixTree() rewrites the entire tree — fine at hundreds of rows, expensive at tens of thousands. On larger tables do the backfill in one deliberate maintenance window with admin closed, never on a live tree while editors are clicking.

Concurrency is the structural weakness, and it scales with people, not data. Two admins dragging the same tree, or a drag racing a delete, can produce an inconsistent tree. The package may not lock. Mitigations in ascending effort:

  • Limit tree editing to a small set of trusted staff.
  • Tell editors not to reorder simultaneously (ugly, effective).
  • If you need real safety, add a coarse advisory lock keyed on the tree root inside your own wrapping action.

Read the field's save path in src/ and confirm whether moves run inside a transaction. This is the crux question, and the source is short enough to answer directly.

Long lock waits at scale. A drag near the top of a large tree rewrites a wide _lft/_rgt range. Invisible at thousands of rows; measure at hundreds of thousands.

Orphaned nodes from bypassed tree handling. Bulk deletes (Model::where(...)->delete()), seeders, and raw SQL leave dangling parent_id references and inconsistent ranges. Symptoms: items vanish from the tree view, or render in impossible positions. The repair command catches it after the fact; the "all writes through the model" rule prevents it.

The field inherits Nova's authorization and adds none. Anyone with resource access can restructure the tree. If hierarchy changes should be privileged, gate the resource or the field via canSee. A mis-drag by a junior editor can reshuffle a production navigation menu.

Deep trees render and drag badly. Test with the depth you actually expect (five or six levels), not a three-node demo. Vue tree components often virtualize poorly; if a large tree makes the index sluggish, split into a read-only summary plus a dedicated "reorder" screen.

Composer constraints are not a compatibility matrix. The composer.json may permit a range of Nova, Laravel, and PHP versions that were never tested together. Verify against your actual runtime rather than trusting the declared range.

Debugging checklist when something looks wrong:

  1. Model::countErrors() — nonzero means the tree is broken, not the UI.
  2. fixTree() in a maintenance window; re-check.
  3. Inspect _lft, _rgt, parent_id on the node you moved — did the write actually land?
  4. Confirm the model really has NodeTrait (easy to miss when you add a second model).
  5. Field renders but never saves? Suspect a Nova major mismatch before anything else.
  6. Drag handles missing entirely? Check dist/js is built and shipped for your Nova version.

Extension points are limited, by design. Customisation happens through field display config and the underlying model. Beyond that, the practical pattern is subclassing the field, and if you need different node markup, overriding the Vue component — at which point you're maintaining a fork. That's fine, as long as you planned for it.

Bus factor and total cost of ownership. Maintained by an agency rather than a solo developer, with a recent release date — a mild positive. But zero dependents means no external bug-finding surface, no SLA, and no compatibility guarantee. Budget internal ownership explicitly: pin it, fork or vendor it, test the invariants, and assign someone to re-evaluate at every Nova upgrade.

When to walk away. No Nova in the stack; flat ordering only; per-user or per-tenant ordering; draft/published ordering states; audited or scheduled reordering; deep, write-heavy, or concurrent hierarchy. For the first, use kalnoy/nestedset directly. For the second, spatie/laravel-sortable. For the rest, you need first-party ownership of the data structure — a Nova field wrapper won't get you there.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console