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

Nova Field Sortable Laravel Package

pixelcreation/nova-field-sortable

Pixelcreation Nova Field Sortable adds drag-and-drop ordering to Laravel Nova fields, making it easy to reorder fields in the Nova UI. This lightweight package helps build more flexible, intuitive admin forms and resource layouts with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a community fork of naxon/nova-field-sortable that gives you a drag handle on a Nova resource's index view for reordering rows. It ships a Vue component plus a small backend route that persists the new order into an integer column. Treat it as a thin, single-purpose field rather than a general sorting framework.

1. Confirm compatibility before anything else. The last release is June 2021, which means Nova 3.x / Laravel 8. Check composer.json in the repo for the nova and illuminate/* constraints. If you're on Nova 4/5 (Vue 3), this fork will not boot — the component uses the Vue 2 Nova.booting registration.

2. Install. If it's on Packagist:

composer require pixelcreation/nova-field-sortable

If not, use a VCS repository entry in composer.json:

"repositories": [{ "type": "vcs", "url": "https://github.com/pixelcreation/nova-field-sortable" }]

3. Add the column. A plain unsigned integer with an index is enough:

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedInteger('sort_order')->default(0)->index();
});

4. Register the field in the resource. The fork most likely retains the original Naxon\NovaFieldSortable namespace — verify in src/ before copying this:

use Naxon\NovaFieldSortable\Sortable;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Title'),
        Sortable::make('Order', 'sort_order'),
    ];
}

5. Order the index by that column. Otherwise the drag handles save values nobody sees:

public static function indexQuery(NovaRequest $request, $query)
{
    return $query->orderBy('sort_order');
}

First use case: a flat, admin-managed list — FAQs, team members, homepage banners, navigation links — where an editor needs to reorder items without editing numbers. That is exactly what this field is for; anything beyond it is where the friction starts.

Where to look first: src/Sortable.php (field definition, default column, visibility flags), routes/api.php (the reorder endpoint), and resources/js/components/ (what payload the frontend actually posts).

Implementation Patterns

Assign order on create. Drag-sorting does nothing for new rows that all land on 0. Pick up the next slot in a model event:

protected static function booted()
{
    static::creating(function ($model) {
        $model->sort_order ??= (int) static::max('sort_order') + 1;
    });
}

Keep the ordering in one place. Rather than repeating orderBy in indexQuery, indexQuery plus every custom query, add a local scope (scopeOrdered) and call it from indexQuery and any API controllers that serve the same list. A global scope works too, but remember it will silently affect ->count(), relationship queries, and anything doing exists() — usually not what you want.

Hide it where it's meaningless. The field is an index-only affordance; it has no useful detail/create/update behaviour. Most forks expose the standard Nova field API, so stack the helpers:

Sortable::make('Order', 'sort_order')
    ->hideFromDetail()
    ->hideWhenCreating()
    ->hideWhenUpdating(),

Scope by parent when lists are nested. If items belong to a category and must be reordered within it, the field alone will not do it — it posts a flat index. Two options: (a) give each parent its own Nova resource index filtered by the parent, so each screen is a flat list; or (b) subclass the field's controller and apply a where('category_id', ...) clause before renumbering. Option (a) is almost always cheaper.

Custom column names. If you name the column position or weight, pass it as the second argument and keep it consistent in your creating hook and orderBy. Don't half-migrate: a resource ordering by sort_order while the field writes to position produces a reorder that "doesn't save."

Test the endpoint, not the UI. The contract you actually depend on is POST to the package's Nova API route with an ordered payload. Find it with:

php artisan route:list | grep -i sort

Then write one feature test that hits it and asserts the column values — that test is your early warning when you eventually upgrade Nova or refactor.

Integration with Nova's own ->sortable(). These are unrelated features despite the name collision. ->sortable() on a Text field gives clickable column headers; this field gives drag handles. They can coexist but will fight over "what the real order is." Pick one and remove the other on that resource.

Gotchas and Tips

The maintenance risk is the headline. Three stars, zero dependents, one release in 2021, and it's already a fork of an abandoned original. Budget for owning this code: vendor it into nova-components/ or plan a migration to spatie/eloquent-sortable + a minimal custom field if you're on Nova 4+. The logic is genuinely small; reimplementing it is a day's work and removes the supply-chain question.

Pagination is the classic failure mode. Drag-and-drop on page 2 of a paginated index computes positions relative to the visible rows. Depending on how the fork's controller renumbers, dropping the first item on page 2 can swap it with the last item on page 1 — or scramble everything. Test with perPage smaller than the dataset, and if it misbehaves, either disable pagination on that resource ($perPageOptions = [100]) or send absolute positions rather than relative indices.

Concurrent drags overwrite each other. Two admins reordering simultaneously produce last-write-wins renumbering. If the list is business-critical, either make sort_order unique and handle the constraint violation with a retry-and-renumber, or accept that a full renumber-on-save (0, 1, 2, …) is self-healing enough for an admin screen.

Ties and gaps. Start values at 1 rather than 0 — 0 is the column default, so a row that never got a sort_order sorts to the front next to genuinely-first items, and ORDER BY ties are non-deterministic in MySQL. Add ->orderBy('id') as a tiebreaker in indexQuery so the list is stable.

Frontend assets. Nova fields compile into public/vendor/<component>. If the drag handle doesn't appear after install, check that the service provider's Nova::script/Nova::style calls run and run php artisan nova:publish, then hard-refresh. A missing handle is nearly always an unpublished asset, not a field-registration bug.

Debugging the save. Open DevTools → Network, drag one row, and inspect the request. If the request 404s, the route isn't registered (service provider not auto-discovered — check config/app.php providers or composer dump-autoload). If it 200s but the order reverts, your indexQuery is overriding the column. If it 403s, you're hitting a Nova authorization check — verify the user's update policy on the resource.

Extension points worth knowing. The field is a standard Laravel\Nova\Fields\Field subclass, so you can override component, add ->withMeta() values, or subclass and swap the field's controller binding if you need custom renumbering logic. That's the cleanest place to add per-parent scoping and gap-free renumbering without forking JS.

Don't reach for it for relationships. BelongsToMany pivot ordering and nested drag-sorting are outside this field's scope; if that's your requirement, start from spatie/eloquent-sortable and a dedicated pivot field rather than bending this one.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource