PHPackages                             johncarter/filament-nested-sortable - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. johncarter/filament-nested-sortable

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

johncarter/filament-nested-sortable
===================================

A Filament plugin for ordering and nesting model records

v2.2.0(3mo ago)025[3 issues](https://github.com/johncarter-/filament-nested-sortable/issues)MITBlade

Since Jul 25Pushed 3mo agoCompare

[ Source](https://github.com/johncarter-/filament-nested-sortable)[ Packagist](https://packagist.org/packages/johncarter/filament-nested-sortable)[ RSS](/packages/johncarter-filament-nested-sortable/feed)WikiDiscussions main Synced today

READMEChangelog (5)Dependencies (2)Versions (6)Used By (0)

Filament Nested Sortable Plugin
===============================

[](#filament-nested-sortable-plugin)

A Filament Panels plugin for ordering and nesting model records.

[![Screenshot](images/screenshot.png)](images/screenshot.png)

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require johncarter/filament-nested-sortable
```

Usage
-----

[](#usage)

Ensure your model has an `order` integer column and an integer `parent_id` with a **default to `-1`**.

### 1. Create a new page in your resource

[](#1-create-a-new-page-in-your-resource)

e.g. `app/Filament/Resources/PageResource/Pages/TreeListPages.php`

Make sure the page extends `JohnCarter\FilamentNestedSortable\Pages\NestedSortablePage`.

```
use JohnCarter\FilamentNestedSortable\Pages\NestedSortablePage;

class TreeListPages extends NestedSortablePage
{
    // ...
}
```

### 2. Add the page to your main PageResource Page

[](#2-add-the-page-to-your-main-pageresource-page)

```
public static function getPages(): array
{
    return [
        'index' => Pages\TreeListPages::route('/'),
    ];
}
```

### 3. Add plugins Tailwind CSS class content

[](#3-add-plugins-tailwind-css-class-content)

Add the plugin's view paths to your `resources/css/filament/cp/theme.css` file:

```
@import '/vendor/johncarter/filament-nested-sortable/resources/views/**/*.blade.php';
```

### 4. Modify the create record form:

[](#4-modify-the-create-record-form)

```
public function getCreateRecordFormSchema(): array
    {
        return [
            TextInput::make('title')
                ->reactive()
                ->afterStateUpdated(
                    fn($state, callable $set) =>
                    $set('slug', Str::slug($state))
                ),

            TextInput::make('slug')
                ->extraAttributes(['class' => 'font-mono text-gray-500']),
        ];
    }
```

### 5. Add actions to the record action group

[](#5-add-actions-to-the-record-action-group)

```
    public function getRecordActions(): array
    {
        $actions = parent::getRecordActions();

        array_unshift($actions, $this->viewAction());

        return $actions;
    }

    public function viewAction(): Action
    {
        return Action::make('view')
            ->icon('heroicon-o-eye')
            ->openUrlInNewTab()
            ->url(function (array $arguments) {
                return $arguments['record']['url'];
            });
    }
```

Customization
-------------

[](#customization)

The package provides several methods and properties that you can override in your page class to customize the default behavior:

### Overridable Properties

[](#overridable-properties)

```
class TreeListPages extends NestedSortablePage
{
    public string $recordKeyName = 'uuid';           // Default: 'id'
    public string $parentColumn = 'category_id';     // Default: 'parent_id'
    public string $orderColumn = 'sort_order';       // Default: 'order'
    public string $childrenRelationName = 'subcategories'; // Default: 'children'
    protected string $view = 'custom::nested-sortable-page'; // Default: 'filament-nested-sortable::pages.nested-sortable-page'
}
```

### Overridable Methods

[](#overridable-methods)

#### Data Retrieval &amp; Display

[](#data-retrieval--display)

```
// Customize record fetching
public function getRecords(): EloquentCollection
{
    return $this->getResource()::getEloquentQuery()
        ->with([$this->childrenRelationName, 'author'])
        ->where('is_published', true)
        ->orderBy($this->orderColumn)
        ->get();
}

// Change label column (default: 'title')
public function getRecordLabelColumn(): string
{
    return 'name';
}

// Enable/disable clickable records (default: true)
public function hasRecordUrl(): bool
{
    return false;
}

// Customize record URLs
public function getRecordUrl($record): string
{
    return route('pages.show', $record['slug']);
}

// Customize label rendering
public function getRecordLabel($record): Htmlable | string
{
    $label = $record->{$this->getRecordLabelColumn()};
    return $this->hasRecordUrl()
        ? new HtmlString('' . $label . '')
        : $label;
}
```

#### Page Configuration

[](#page-configuration)

```
// Customize page title
public function getTitle(): string
{
    return 'Category Management';
}
```

#### Record Actions

[](#record-actions)

```
// Add/remove actions
public function getRecordActions(): array
{
    $actions = parent::getRecordActions();
    array_unshift($actions, $this->viewAction());
    return $actions;
}

// Override edit action
public function editAction(): Action
{
    return Action::make('edit')
        ->icon('heroicon-o-pencil')
        ->color('warning')
        ->url(fn($arguments) => $this->getResource()::getUrl('edit', ['record' => $arguments['record'][$this->recordKeyName]]));
}

// Override delete action
public function deleteAction(): Action
{
    return Action::make('delete')
        ->label('Remove')
        ->color('danger')
        ->requiresConfirmation()
        ->action(fn($arguments) => $this->deleteRecord($arguments['record'][$this->recordKeyName]));
}
```

#### Header Actions

[](#header-actions)

```
// Customize header actions
public function getHeaderActions(): array
{
    return [
        Action::make('create')
            ->label('Add New Category')
            ->schema($this->getCreateRecordFormSchema())
            ->action(fn($data) => $this->createRecord($data)),
        Action::make('import')
            ->url(route('categories.import')),
    ];
}
```

#### Form &amp; Creation

[](#form--creation)

```
// Customize create form
public function getCreateRecordFormSchema(): array
{
    return [
        TextInput::make('name')->required(),
        TextInput::make('slug')->unique('categories'),
        Select::make('parent_id')->options($this->getParentOptions()),
    ];
}

// Customize creation logic
public function createRecord(array $data): void
{
    $data['order'] = $this->getNextOrder();
    $this->getResource()::getModel()::create($data);
    $this->records = $this->getRecords();
}
```

#### Record Updates

[](#record-updates)

```
// Customize update logic
public function persistRecordUpdates($pendingRecordUpdates)
{
    foreach ($pendingRecordUpdates as $update) {
        $record = $this->getResource()::getModel()::find($update[$this->recordKeyName]);
        $record->update($update);
    }
    $this->records = $this->getRecords();
    $this->dispatch('reset-pending-record-updates');
}
```

These customization options allow you to tailor the nested sortable functionality to match your specific application requirements while maintaining the core drag-and-drop ordering and nesting capabilities.

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance78

Regular maintenance activity

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity40

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~56 days

Total

5

Last Release

118d ago

Major Versions

v1.1.0 → v2.0.02025-08-08

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/813018?v=4)[johncarter](/maintainers/johncarter)[@JohnCarter](https://github.com/JohnCarter)

---

Top Contributors

[![johncarter-](https://avatars.githubusercontent.com/u/3776888?v=4)](https://github.com/johncarter- "johncarter- (41 commits)")

### Embed Badge

![Health badge](/badges/johncarter-filament-nested-sortable/health.svg)

```
[![Health](https://phpackages.com/badges/johncarter-filament-nested-sortable/health.svg)](https://phpackages.com/packages/johncarter-filament-nested-sortable)
```

###  Alternatives

[ysfkaya/filament-phone-input

A phone input component for Laravel Filament

3161.3M25](/packages/ysfkaya-filament-phone-input)[stephenjude/filament-feature-flags

Filament implementation of feature flags and segmentation with Laravel Pennant.

122177.8k1](/packages/stephenjude-filament-feature-flags)[marcelweidum/filament-expiration-notice

Customize the livewire expiration notice

94135.4k5](/packages/marcelweidum-filament-expiration-notice)[stephenjude/filament-blog

Filament Blog Builder

20619.4k](/packages/stephenjude-filament-blog)[crumbls/layup

A visual page builder plugin for Filament 5 — Divi-style grid layouts with extensible widgets.

592.6k2](/packages/crumbls-layup)[eduardoribeirodev/filament-leaflet

Um widget de mapa para FilamentPHP.

2118.9k](/packages/eduardoribeirodev-filament-leaflet)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
