optimistdigital/nova-sortable
A Laravel Nova package for drag-and-drop sorting of resource records in index tables. Add a sortable trait and field, then reorder rows directly in Nova with minimal setup. Ideal for models using an order or sort_order column.
Architecture fit. The package is a thin, additive Nova extension: a model trait plus a Nova field that renders drag handles in the resource index and persists a sort_order integer. It does not alter your domain model, introduce new services, or impose runtime infrastructure. For any Nova-backed admin where editors need manual merchandising order (CMS pages, FAQs, navigation items, menu categories), this is a near-perfect architectural fit — it solves one narrow problem at the presentation layer and stays out of the way of the rest of the application.
Integration feasibility. Low. The work is: composer require, a migration adding a nullable integer sort column, a trait on the model, one field entry in the resource's fields() method, and a published asset. Realistically a half-day for a developer familiar with Nova, plus migration/backfill time. There is no API surface, no queue, no external service. The main unknown is not effort but fit with your existing ordering semantics.
Technical risk. Moderate and concentrated in four places:
sort_order values. The package will not arbitrate this for you.Key questions to resolve before committing.
orderBy calls, global scopes, or a default index sort in the resource?Stack fit. Laravel + Nova + Composer. Requirements are a persisted integer column and a model trait; the Nova field is the UI. Fit is clean provided the application does not already own an ordering concern for the same model (e.g., an orderBy('position') baked into a repository or API contract). If it does, the package's column must either become the source of truth or be reconciled with it — decide this explicitly, because a second ordering authority is the most common way this integration goes wrong.
Migration path. Do the schema change online and additively:
sort_order (or your chosen column) with a composite index on (parent_key, sort_order) if ordering is scoped — this is the difference between a fast index scan and a filesort.created_at, id) rather than leaving NULLs, so the initial ordering is stable and reproducible across environments.Compatibility. Pin the package version in composer.json alongside the Nova constraint, and treat Nova upgrades as a coupled change: bump Nova and this package in the same PR, with a smoke test that drag-and-drop actually persists. Do not let the package float on a ^ constraint against a closed-source dependency.
Sequencing. (1) Spike in a branch against a realistic dataset size to measure reorder latency and confirm scoping behaviour. (2) Schema migration and backfill, deployed without UI. (3) Trait + field behind a flag, enabled for internal admins first. (4) Enable broadly, with a documented "ordering owner" per resource. (5) Add the Nova-upgrade check to your recurring maintenance checklist. Total elapsed time is short; the sequencing exists to de-risk the data and the upgrade path, not the install.
Maintenance. Ongoing cost is low but non-zero: periodic Composer updates, a compatibility check on every Nova upgrade, and monitoring of the upstream repo for abandonment. With a single-agency maintainer and no dependents, assign an internal owner and keep the fork option warm. The code footprint is small enough that a fork is hours, not weeks — but only if you have not patched it in place without tracking.
Support. Community-only: GitHub issues, no SLA, no vendor escalation. This is acceptable for an admin-only convenience feature and unacceptable if ordering becomes business-critical (e.g., customer-facing curation). Classify it accordingly before you build process around it.
Scaling. Reorder cost scales with the number of rows in the sort group, not the number of rows dragged. Keep sort groups small via parent scoping where the domain allows. On large flat tables, measure the reorder transaction time early and set a threshold above which the feature is disabled or re-architected (e.g., sparse ordering with gap-based positioning). The read path is cheap if the composite index exists; without it, every index view pays for a sort.
Failure modes to watch.
sort_order values after concurrent edits or a partial backfill, producing items that jump between pages.Ramp-up. Developer ramp-up is negligible — an hour with the README. The real ramp-up is organisational: define who owns ordering per resource, document that ordering is per-page/per-scope, and decide whether ordering is advisory or contractual. That definition, not the code, is what determines whether this package stays a 285-star convenience or becomes an unowned dependency in your critical path.
How can I help you explore Laravel packages today?