PHPackages                             wezlo/filament-kanban - 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. wezlo/filament-kanban

ActiveLibrary

wezlo/filament-kanban
=====================

Advanced Kanban Board for Filament v5 Resources

1.0.0(2d ago)41↑2900%MITPHPPHP ^8.3

Since Apr 6Pushed 2d agoCompare

[ Source](https://github.com/mustafakhaleddev/filament-kanban)[ Packagist](https://packagist.org/packages/wezlo/filament-kanban)[ RSS](/packages/wezlo-filament-kanban/feed)WikiDiscussions master Synced today

READMEChangelog (1)Dependencies (5)Versions (1)Used By (0)

Filament Kanban Board
=====================

[](#filament-kanban-board)

An advanced Kanban Board package for Filament v5. Drop it into any Resource's List page to replace the table with a fully interactive board.

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 13+
- Filament 5+

Features
--------

[](#features)

- Drag-and-drop cards between columns (SortableJS)
- Enum-based or relationship-based columns
- `KanbanStatusEnum` interface for defining transitions &amp; WIP limits on the enum itself
- Card click action (modal, slide-over, or custom)
- Card footer actions (edit, delete, URL navigation, custom)
- Column header actions (create with pre-filled status)
- Filters dropdown with active count badge &amp; reset
- Search bar with relationship support
- Collapsible columns (persisted in localStorage)
- WIP limits with visual warnings and server-side enforcement
- Column summaries (aggregates)
- Empty state per column
- Drag constraints (client-side + server-side)
- `canMove()` callback for custom authorization
- Resource policy authorization on every move
- Loading indicator
- Custom views (card, column, board)
- Dark mode support
- Accessibility (ARIA roles, labels, keyboard-friendly)
- Publishable Blade views
- Error notifications on failed moves

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

[](#installation)

```
composer require wezlo/filament-kanban
```

Register the plugin in your Panel Provider:

```
use Wezlo\FilamentKanban\FilamentKanbanPlugin;

->plugins([
    FilamentKanbanPlugin::make(),
])
```

Quick Start
-----------

[](#quick-start)

Add `HasKanbanBoard` to your Resource's List page and define `kanban()`:

```
use Wezlo\FilamentKanban\Concerns\HasKanbanBoard;
use Wezlo\FilamentKanban\KanbanBoard;

class ListLeads extends ListRecords
{
    use HasKanbanBoard;

    protected static string $resource = LeadResource::class;

    public function kanban(KanbanBoard $kanban): KanbanBoard
    {
        return $kanban
            ->enumColumn('status', LeadStatus::class)
            ->cardTitle(fn ($record) => $record->title)
            ->cardDescription(fn ($record) => $record->assignee?->name);
    }
}
```

The board replaces the table. Columns are generated from your enum. The breadcrumb shows "Board" instead of "List".

KanbanStatusEnum Interface
--------------------------

[](#kanbanstatusenum-interface)

For full integration, implement `KanbanStatusEnum` on your enum. This lets you define allowed transitions and WIP limits directly on the enum -- no board configuration needed.

```
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Icons\Heroicon;
use Wezlo\FilamentKanban\Contracts\KanbanStatusEnum;

enum LeadStatus: string implements HasIcon, KanbanStatusEnum
{
    case New = 'new';
    case Contacted = 'contacted';
    case SiteVisit = 'site_visit';
    case Negotiation = 'negotiation';
    case Won = 'won';
    case Lost = 'lost';

    // Required by HasLabel (via KanbanStatusEnum)
    public function getLabel(): string
    {
        return match ($this) {
            self::New => 'New',
            self::Contacted => 'Contacted',
            // ...
        };
    }

    // Required by HasColor (via KanbanStatusEnum)
    public function getColor(): string
    {
        return match ($this) {
            self::New => 'info',
            self::Contacted => 'warning',
            // ...
        };
    }

    // Optional: HasIcon
    public function getIcon(): Heroicon
    {
        return match ($this) {
            self::New => Heroicon::Sparkles,
            // ...
        };
    }

    // Define which statuses each status can transition to.
    // Return null to allow all transitions.
    public function getAllowedTransitions(): ?array
    {
        return match ($this) {
            self::New => [self::Contacted, self::Lost],
            self::Contacted => [self::SiteVisit, self::Lost],
            self::SiteVisit => [self::Negotiation, self::Lost],
            self::Negotiation => [self::Won, self::Lost],
            self::Won => null,  // no constraints
            self::Lost => null,
        };
    }

    // Set max cards per column. Return null for unlimited.
    public function getWipLimit(): ?int
    {
        return match ($this) {
            self::Negotiation => 10,
            default => null,
        };
    }
}
```

The board automatically reads these -- just use `->enumColumn('status', LeadStatus::class)` and transitions + WIP limits are enforced both client-side and server-side.

**Without the interface:** Regular `BackedEnum` with `HasLabel` + `HasColor` still works. You just configure constraints on the board instead.

**Explicit overrides:** Board-level `->dragConstraints()` and `->wipLimits()` override enum values per column.

Configuration
-------------

[](#configuration)

### Column Source

[](#column-source)

**Enum-based** (columns from a BackedEnum):

```
->enumColumn('status', LeadStatus::class)
```

**Relationship-based** (columns from a related model):

```
->relationshipColumn('stage', 'name', Stage::class, orderAttribute: 'sort_order')
```

### Card Content

[](#card-content)

```
->cardTitle(fn ($record) => $record->title)
->cardDescription(fn ($record) => $record->assignee?->name)
->cardBadges(fn ($record) => [
    ['label' => $record->priority->getLabel(), 'color' => $record->priority->getColor()],
])
```

### Card Click Action

[](#card-click-action)

Pass any Filament Action to fire when a card is clicked:

```
use Filament\Actions\Action;
use Filament\Infolists\Components\TextEntry;

->cardAction(
    Action::make('view')
        ->slideOver()
        ->schema([
            TextEntry::make('title'),
            TextEntry::make('status')->badge(),
        ])
        ->fillForm(fn ($record) => $record->toArray())
        ->modalSubmitAction(false)
        ->modalCancelActionLabel('Close')
)
```

Clicking opens the modal. Dragging still works -- the package distinguishes clicks from drags using SortableJS events.

### Card Footer Actions

[](#card-footer-actions)

Icon buttons at the bottom of each card. Actions with `->url()` render as links, others use Livewire modals.

```
use Filament\Actions\Action;
use Filament\Support\Icons\Heroicon;

->cardFooterActions([
    Action::make('edit')
        ->icon(Heroicon::PencilSquare)
        ->color('gray')
        ->url(fn ($record) => LeadResource::getUrl('edit', ['record' => $record])),
    Action::make('delete')
        ->icon(Heroicon::Trash)
        ->color('danger')
        ->requiresConfirmation()
        ->action(fn ($record) => $record->delete()),
])
```

### Column Header Action

[](#column-header-action)

"+" button per column. The column value is pre-filled into the form.

```
use Filament\Actions\CreateAction;

->columnHeaderAction(CreateAction::make())
```

### Filters

[](#filters)

Renders as a dropdown panel triggered by a filter icon next to the search bar. Shows active filter count as a badge.

```
use Filament\Forms\Components\Select;

->filters([
    Select::make('priority')
        ->options(LeadPriority::class)
        ->placeholder('All Priorities'),
    Select::make('assigned_to')
        ->relationship('assignee', 'name')
        ->placeholder('All Assignees'),
])
->filtersColumns(2) // grid columns inside the dropdown
```

### Search

[](#search)

```
->searchable(['title', 'client.user.name'])
```

Supports dot notation for relationship columns.

### Collapsible Columns

[](#collapsible-columns)

```
->collapsible()
```

State persisted per column in `localStorage`.

### WIP Limits

[](#wip-limits)

Define on the enum via `KanbanStatusEnum::getWipLimit()`, or on the board:

```
->wipLimits(['new' => 5, 'in_progress' => 10])
->defaultWipLimit(20)
```

The count badge turns red when over limit. Moves into over-limit columns are **blocked server-side** with a notification.

### Column Summaries

[](#column-summaries)

```
->columnSummary(function ($records, $column) {
    $total = $records->sum('estimated_budget');
    return $total > 0 ? 'SAR ' . number_format($total, 0) : null;
})
```

### Empty State

[](#empty-state)

```
->emptyState('No leads', 'Drag leads here or create a new one')
```

### Drag Constraints

[](#drag-constraints)

Define on the enum via `KanbanStatusEnum::getAllowedTransitions()`, or on the board:

```
->dragConstraints([
    'new' => [LeadStatus::Contacted, LeadStatus::Lost],
    'contacted' => [LeadStatus::SiteVisit, LeadStatus::Lost],
])
```

Enforced both client-side (SortableJS `put` function) and server-side (before DB update).

### Authorization

[](#authorization)

**Resource policy:** The package checks `Resource::canEdit($record)` before every move. Unauthorized moves show a danger notification.

**canMove callback:** Custom business logic:

```
->canMove(function ($record, $oldStatus, $newStatus) {
    if ($newStatus === 'won') {
        return auth()->user()->hasRole('project-manager');
    }
    return true;
})
```

**Order of checks:** Resource policy -&gt; Drag constraints -&gt; WIP limits -&gt; canMove callback. First failure blocks the move.

### Move Callback

[](#move-callback)

Run logic after a successful move:

```
->onRecordMoved(function ($record, $fromValue, $toValue) {
    activity()->performedOn($record)->log("Moved from {$fromValue} to {$toValue}");
})
```

### Query Customization

[](#query-customization)

```
->modifyQueryUsing(fn ($query) => $query->where('company_id', auth()->user()->company_id))
->recordsPerColumn(50)
->excludeColumns([LeadStatus::Lost])
```

### Column Appearance

[](#column-appearance)

```
->columnWidth('320px')
->columnColor(fn ($column) => $column->color ?? 'gray')
```

### Custom Views

[](#custom-views)

Override any view:

```
->cardView('leads.kanban.card')     // receives $record, $board, $column
->columnView('leads.kanban.column') // receives $column, $board
->boardView('leads.kanban.board')
```

Or publish all views:

```
php artisan vendor:publish --tag=filament-kanban-views
```

### Loading Indicator

[](#loading-indicator)

Enabled by default. Shows a spinner overlay during Livewire updates.

```
->loading(false) // disable
```

Custom Theme
------------

[](#custom-theme)

If you have a custom Filament theme, add the package views to your `@source` directive:

```
@source '../../../../vendor/wezlo/filament-kanban/resources/views/**/*';
```

Testing
-------

[](#testing)

```
php artisan test --filter=KanbanBoard
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 88% of packages

Maintenance99

Actively maintained with recent releases

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

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

Unknown

Total

1

Last Release

2d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/df745a14d08940e520abaf3b7cbbbaba551b14f0f73b530b9801cdafb75fabfa?d=identicon)[mustafakhaleddev](/maintainers/mustafakhaleddev)

---

Top Contributors

[![mustafakhaleddev](https://avatars.githubusercontent.com/u/25182746?v=4)](https://github.com/mustafakhaleddev "mustafakhaleddev (2 commits)")

---

Tags

laravelfilamentdrag-dropkanban

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/wezlo-filament-kanban/health.svg)

```
[![Health](https://phpackages.com/badges/wezlo-filament-kanban/health.svg)](https://phpackages.com/packages/wezlo-filament-kanban)
```

###  Alternatives

[bezhansalleh/filament-shield

Filament support for `spatie/laravel-permission`.

2.8k2.9M88](/packages/bezhansalleh-filament-shield)[jibaymcs/filament-tour

Bring the power of DriverJs to your Filament panels and start a tour !

12247.8k](/packages/jibaymcs-filament-tour)[guava/filament-modal-relation-managers

Allows you to embed relation managers inside filament modals.

7565.0k4](/packages/guava-filament-modal-relation-managers)[mwguerra/filemanager

A full-featured file manager package for Laravel and Filament v5 with dual operating modes, drag-and-drop uploads, S3/MinIO support, and comprehensive security features.

718.5k1](/packages/mwguerra-filemanager)[agencetwogether/hookshelper

Simple plugin to toggle display hooks available in current page.

2312.7k](/packages/agencetwogether-hookshelper)[tapp/filament-webhook-client

Add a Filament resource and a policy for Spatie Webhook client

1120.2k](/packages/tapp-filament-webhook-client)

PHPackages © 2026

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