Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Eloquent Sortable
https://github.com/spatie/eloquent-sortable
Admin
Eloquent Sortable is a Laravel package that adds sortable behavior to Eloquent models, automatically
...
Tokens:
7,515
Snippets:
54
Trust Score:
8.5
Update:
2 months ago
Context
Skills
Chat
Benchmark
78.5
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Eloquent Sortable Eloquent Sortable is a Laravel package that provides sortable behavior for Eloquent models. It automatically manages an order column on your models, assigning sequential order values when new records are created and providing methods to reorder, move, and swap model positions. The package offers a trait-based implementation that integrates seamlessly with Laravel's Eloquent ORM. It supports soft deletes, grouped sorting (e.g., sorting within a parent relationship), and dispatches events after sorting operations. The package is compatible with Laravel 9.x through 12.x and requires PHP 8.1+. ## Installation Install via Composer and optionally publish the configuration file. ```bash # Install the package composer require spatie/eloquent-sortable # Optionally publish the config file php artisan vendor:publish --tag=eloquent-sortable-config ``` ## Basic Model Setup Implement the `Sortable` interface and use the `SortableTrait` to add sortable behavior to any Eloquent model. ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Spatie\EloquentSortable\Sortable; use Spatie\EloquentSortable\SortableTrait; class Task extends Model implements Sortable { use SortableTrait; protected $fillable = ['name', 'order_column']; // Optional: customize sortable behavior public $sortable = [ 'order_column_name' => 'order_column', 'sort_when_creating' => true, ]; } // Creating models automatically assigns order values $task1 = Task::create(['name' => 'First task']); // order_column = 1 $task2 = Task::create(['name' => 'Second task']); // order_column = 2 $task3 = Task::create(['name' => 'Third task']); // order_column = 3 // Retrieve records in order $orderedTasks = Task::ordered()->get(); // Returns: First task (1), Second task (2), Third task (3) ``` ## ordered() - Query Scope for Ordered Results The `ordered()` scope returns models sorted by their order column. Accepts an optional direction parameter. ```php <?php use App\Models\Task; // Get all tasks in ascending order (default) $tasks = Task::ordered()->get(); // Get all tasks in descending order $tasksDesc = Task::ordered('desc')->get(); // Combine with other query conditions $completedTasks = Task::where('status', 'completed') ->ordered() ->get(); // Paginate ordered results $paginatedTasks = Task::ordered()->paginate(10); ``` ## setNewOrder() - Bulk Reorder by IDs The `setNewOrder()` method reorders all records based on an array of IDs. The first ID gets order 1, second gets order 2, etc. ```php <?php use App\Models\Task; // Reorder tasks: task ID 3 becomes first, ID 1 second, ID 2 third Task::setNewOrder([3, 1, 2]); // Start from a custom order number (e.g., 10) // Results: ID 3 = order 10, ID 1 = order 11, ID 2 = order 12 Task::setNewOrder([3, 1, 2], 10); // Works with Collections too $ids = Task::where('project_id', 5)->pluck('id')->shuffle(); Task::setNewOrder($ids); // Modify the query (e.g., bypass global scopes) Task::setNewOrder([3, 1, 2], 1, null, function ($query) { $query->withoutGlobalScope('ActiveScope'); }); ``` ## setNewOrderByCustomColumn() - Reorder by Custom Column Use `setNewOrderByCustomColumn()` when your identifier is not the primary key (e.g., UUID). ```php <?php use App\Models\Task; // Reorder using UUID column instead of primary key Task::setNewOrderByCustomColumn('uuid', [ '7a051131-d387-4276-bfda-e7c376099715', // order = 1 '40324562-c7ca-4c69-8018-aff81bff8c95', // order = 2 '5dc4d0f4-0c88-43a4-b293-7c7902a3cfd1', // order = 3 ]); // With custom starting order Task::setNewOrderByCustomColumn('uuid', [ '7a051131-d387-4276-bfda-e7c376099715', // order = 10 '40324562-c7ca-4c69-8018-aff81bff8c95', // order = 11 '5dc4d0f4-0c88-43a4-b293-7c7902a3cfd1', // order = 12 ], 10); // Reorder by slug Task::setNewOrderByCustomColumn('slug', [ 'urgent-task', 'normal-task', 'low-priority-task', ]); ``` ## moveOrderUp() / moveOrderDown() - Move Single Position Move a model one position up or down in the order sequence by swapping with the adjacent model. ```php <?php use App\Models\Task; $task = Task::find(3); // Currently order_column = 3 // Move down one position (swap with order 4) $task->moveOrderDown(); // $task now has order_column = 4, previous order 4 model now has order 3 // Move up one position (swap with order 3) $task->moveOrderUp(); // $task now has order_column = 3 // Methods return $this for chaining, and safely handle edge cases $firstTask = Task::ordered()->first(); $firstTask->moveOrderUp(); // No change, already at top $lastTask = Task::ordered('desc')->first(); $lastTask->moveOrderDown(); // No change, already at bottom ``` ## moveToStart() / moveToEnd() - Move to Extreme Position Move a model to the very beginning or end of the ordered list. ```php <?php use App\Models\Task; // Tasks: A(1), B(2), C(3), D(4), E(5) $taskC = Task::find(3); // order_column = 3 // Move to first position $taskC->moveToStart(); // Result: C(1), A(2), B(3), D(4), E(5) // Move to last position $taskC->moveToEnd(); // Result: A(1), B(2), D(3), E(4), C(5) // Methods return $this for chaining $task = Task::find(1) ->moveToEnd() ->update(['highlighted' => true]); ``` ## moveAfter() / moveBefore() - Move Relative to Another Model Position a model directly after or before a specific target model. ```php <?php use App\Models\Task; // Tasks: A(1), B(2), C(3), D(4), E(5) $taskE = Task::find(5); // order_column = 5 $taskB = Task::find(2); // order_column = 2 // Move E directly after B $taskE->moveAfter($taskB); // Result: A(1), B(2), E(3), C(4), D(5) // Move E directly before B $taskE->moveBefore($taskB); // Result: A(1), E(2), B(3), C(4), D(5) // Useful for drag-and-drop reordering $draggedTask = Task::find($request->dragged_id); $targetTask = Task::find($request->target_id); if ($request->position === 'after') { $draggedTask->moveAfter($targetTask); } else { $draggedTask->moveBefore($targetTask); } ``` ## swapOrder() - Swap Two Models Exchange the order positions of two models. ```php <?php use App\Models\Task; $task1 = Task::find(1); // order_column = 1 $task2 = Task::find(3); // order_column = 3 // Static method: swap positions Task::swapOrder($task1, $task2); // $task1 now has order_column = 3 // $task2 now has order_column = 1 // Instance method alternative $task1->swapOrderWithModel($task2); // Useful for "move up/down" UI buttons that need to swap $currentTask = Task::find($id); $nextTask = Task::where('order_column', '>', $currentTask->order_column) ->ordered() ->first(); if ($nextTask) { Task::swapOrder($currentTask, $nextTask); } ``` ## isFirstInOrder() / isLastInOrder() - Position Check Check if a model is at the first or last position in the order sequence. ```php <?php use App\Models\Task; $task = Task::find(1); // Check position if ($task->isFirstInOrder()) { // Disable "Move Up" button in UI } if ($task->isLastInOrder()) { // Disable "Move Down" button in UI } // Useful for conditional UI rendering $tasks = Task::ordered()->get()->map(function ($task) { return [ 'id' => $task->id, 'name' => $task->name, 'canMoveUp' => !$task->isFirstInOrder(), 'canMoveDown' => !$task->isLastInOrder(), ]; }); ``` ## getHighestOrderNumber() / getLowestOrderNumber() - Order Boundaries Get the current maximum or minimum order value in the table. ```php <?php use App\Models\Task; $task = new Task(); // Get the highest order number currently in use $maxOrder = $task->getHighestOrderNumber(); // e.g., 25 // Get the lowest order number currently in use $minOrder = $task->getLowestOrderNumber(); // e.g., 1 // Useful for manual order assignment $newTask = new Task(['name' => 'New task']); $newTask->order_column = $task->getHighestOrderNumber() + 1; $newTask->save(); ``` ## buildSortQuery() - Grouped Sorting Override `buildSortQuery()` to scope sorting within groups (e.g., tasks within a project). ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Spatie\EloquentSortable\Sortable; use Spatie\EloquentSortable\SortableTrait; class Task extends Model implements Sortable { use SortableTrait; protected $fillable = ['name', 'project_id', 'order_column']; // Scope sorting to within each project public function buildSortQuery(): Builder { return static::query()->where('project_id', $this->project_id); } } // Now each project has independent ordering $project1Task = Task::create(['name' => 'Task A', 'project_id' => 1]); // order = 1 in project 1 $project1Task2 = Task::create(['name' => 'Task B', 'project_id' => 1]); // order = 2 in project 1 $project2Task = Task::create(['name' => 'Task C', 'project_id' => 2]); // order = 1 in project 2 // Moving within a project only affects that project's tasks $project1Task->moveOrderDown(); // Swaps with Task B, doesn't affect project 2 // Get ordered tasks for a specific project $project1Tasks = Task::where('project_id', 1)->ordered()->get(); ``` ## EloquentModelSortedEvent - Sort Event Listener Listen for the `EloquentModelSortedEvent` dispatched after sorting operations to trigger post-sort actions. ```php <?php namespace App\Listeners; use Spatie\EloquentSortable\EloquentModelSortedEvent; use App\Models\Task; use Illuminate\Support\Facades\Cache; class ClearTaskCacheListener { public function handle(EloquentModelSortedEvent $event): void { // Check which model was sorted if ($event->isFor(Task::class)) { Cache::forget('tasks.ordered'); Cache::forget('tasks.homepage'); } // Access the model class name $modelClass = $event->model; // e.g., "App\Models\Task" } } // Register in EventServiceProvider protected $listen = [ \Spatie\EloquentSortable\EloquentModelSortedEvent::class => [ \App\Listeners\ClearTaskCacheListener::class, ], ]; ``` ## Configuration Options Configure default behavior via config file or model properties. ```php <?php // config/eloquent-sortable.php return [ // Default column name for storing order 'order_column_name' => 'order_column', // Auto-assign order when creating new models 'sort_when_creating' => true, // Don't update updated_at when reordering 'ignore_timestamps' => false, ]; // Override per model class Task extends Model implements Sortable { use SortableTrait; public $sortable = [ 'order_column_name' => 'position', // Use 'position' column 'sort_when_creating' => false, // Don't auto-assign on create ]; } // Disable auto-sorting for bulk imports class ImportedTask extends Model implements Sortable { use SortableTrait; public $sortable = [ 'sort_when_creating' => false, ]; } // After import, manually set order ImportedTask::setNewOrder(ImportedTask::pluck('id')->toArray()); ``` ## Summary Eloquent Sortable is ideal for any Laravel application that needs to maintain ordered lists of records, such as task managers, content management systems with sortable pages or menu items, e-commerce product catalogs, playlist managers, or any drag-and-drop reordering interface. The package handles all the complexity of maintaining sequential order values, including shifting other records when items are moved. Integration is straightforward: add an integer `order_column` to your migration, implement the `Sortable` interface, use the `SortableTrait`, and optionally override `buildSortQuery()` for grouped sorting. The package works seamlessly with soft deletes, global scopes, and custom primary keys (UUIDs). For frontend integration, combine the `setNewOrder()` method with a JavaScript sortable library to implement drag-and-drop reordering with minimal backend code.