packstub/filament-kanban
| Install | |
|---|---|
composer require packstub/filament-kanban |
|
| Latest Version: | v0.2.0 |
| PHP: | ^8.3 |
| Laravel: | 12 · 13 |
| License: | MIT |
| Last Updated: | Nov 3, 2026 |
| Links: | GitHub · Packagist |
Filament Kanban
A fast, simple Kanban board for Filament: a drop lands at once, the server checks every rule, and your Filament actions work on every card.

Features
- Instant drag and drop: cards land at once; a refused move slides back.
- Rules on the server: enum columns, drop rules, WIP limits, locked cards.
- Filament actions on cards and columns: edit, delete, create in a column, or your own.
- Column summaries: a sum or an average under each column title.
- Rich, fast cards: badges, due dates, progress, avatars; hundreds stay fast.
- Search, filters and paging: Filament's filter fields, kept in the URL.
- Swimlanes: rows by assignee, priority or any attribute.
- Bulk selection: move or act on many cards at once.
- Shared boards: realtime with Echo or polling, Undo on every move.
- Keyboard, screen readers and phones: every move works without a mouse.
- Dark mode and translations: English, Romanian, Russian and German.
Compatibility
| Plugin | Filament | Laravel | PHP |
|---|---|---|---|
| 0.x | 4.x, 5.x | 12.x, 13.x | 8.3+ |
Installation
composer require packstub/filament-kanban
php artisan filament:assets
No config, no migrations. The board uses the SortableJS that Filament already ships and plain CSS on your panel's palette, so there is nothing to add to your theme.
Add a board page next to a resource (or a standalone KanbanPage) and describe the board in one method:
use App\Enums\DealStage;
use App\Models\Deal;
use Packstub\Kanban\Board;
use Packstub\Kanban\Card;
use Packstub\Kanban\Pages\KanbanResourcePage;
class DealBoard extends KanbanResourcePage
{
protected static string $resource = DealResource::class;
public function kanban(Board $board): Board
{
return $board
->query(fn () => Deal::query()->with('owner'))
->columnAttribute('stage')
->columns(DealStage::class)
->card(fn (Deal $deal) => Card::make()
->eyebrow($deal->reference)
->title($deal->company)
->aside(Number::currency($deal->amount, 'USD'))
->meta([$deal->title, $deal->close_at?->format('M j')])
->avatar($deal->owner?->avatar_url, $deal->owner?->name));
}
}
Register it in the resource's getPages(): 'kanban' => DealBoard::route('/board'). Under a resource the query() is optional (the board shows the resource's records, tenant and scopes included), KanbanAction::make() in the list page's header gives a Table / Board switch, and the HasKanbanNavigationBadge trait on the resource puts the number of cards in the navigation.
Read more: Installation, including a board inside any Livewire component.
Instant moves

A drop moves the card in the browser at once and asks the server with a small renderless call. The server loads the record through the board's query() and checks the column rules; your move logic runs; the card flashes when it is saved, or slides back with the reason when it is refused. Give your own logic to moveUsing() and throw to refuse:
->moveUsing(function (Deal $deal, string $to, string $from) {
if ($to === 'won' && ! $deal->contract_signed) {
throw new \RuntimeException('Attach the signed contract first.');
}
$deal->update(['stage' => $to]);
})
Scope the query (tenant, team, date window) and every read, move and action goes through it.
Read more: Moves and events.
Columns and rules

Build the columns from an enum, or list them. Every rule takes a closure and is evaluated once per request, never per card:
->columns([
Column::make('todo')->label('To do')->color('sky'),
Column::make('doing')->color('amber')->accepts(['todo'])->limit(5),
Column::make('review')->color('violet')
->accepts(['doing'])
->visible(fn () => auth()->user()->isReviewer()),
Column::make('done')->color('emerald')
->accepts(['review'])
->droppable(fn () => auth()->user()->can('ship'))
->collapsed(),
])
| Method | What it does |
|---|---|
visible() / hidden() |
Leave the column out for this user: its cards are not loaded and nothing moves into or out of it. |
accepts([...]) |
The columns a card may come from. null (default) means anywhere. |
droppable(), draggable(), readOnly() |
Who may drop in and drag out. |
limit() |
A WIP limit: 3/5 in the header, and a full column refuses moves and new cards. |
creatable() |
Whether the column offers "+" for the create action. |
collapsed(), sortBy() |
Start folded; this column's order. |
Read more: Columns.
Card actions

Your Filament actions work on cards, with their forms, modals, slide-overs and confirmations. The card's record is the action's record, loaded through the board's query, and the board refreshes once an action has run. cardAction() picks the one a click on the card runs:
->cardActions([
EditAction::make()->schema(DealResource::fields())->slideOver(),
Action::make('hot')->icon(Heroicon::OutlinedFire)->action(fn (Deal $record) => $record->update(['is_hot' => true])),
DeleteAction::make(),
])
->cardAction('edit')

Read more: Actions.
Create in a column

createAction() puts a "+" in each droppable column's header. The form opens, and the new record gets that column:
->createAction(CreateAction::make()->label('New deal')->schema([...])->slideOver())
Hidden, read-only, full and creatable(false) columns get no "+", and the server refuses a create into them.
Read more: Actions.
Summaries and limits
A line under each column title, from the column's query with the current search and filters, refreshed after every move:
->summarize(fn (Builder $query) => Number::currency($query->sum('amount'), 'USD'))
Read more: Columns.
Cards
Card::make() with eyebrow() (small monospaced line), title(), aside() (right-aligned: an amount, a date), description() (two muted lines), badge($label, $color, $condition, icon: ...), meta([...]) (empty parts dropped), due($date) (red once past, amber on the day), progress($done, $total) (a thin bar), avatar($url, $name) (initials without a picture), accent($color) (a coloured left edge), url(), searchText(), actions([...]) and locked() (one card that stays put, refused by the server too).
Read more: Cards.
Search, filters and paging

->searchable(['reference', 'company', 'owner.name'])
->filters([
Filter::select('owner_id')->label('Owners')->multiple()->options(fn () => User::pluck('name', 'id')->all()),
Filter::toggle('mine')->label('Assigned to me')->query(fn (Builder $query) => $query->where('owner_id', auth()->id())),
Filter::dateRange('due_at'),
])
->filtersLayout(FiltersLayout::Dropdown) // behind a funnel button (the default), Toolbar, or AboveContent
->perColumn(50)
Typing filters the loaded cards at once, then the server brings the matching cards of every column. A Filter is one of Filament's own fields: a select (one option, several with multiple(), typed with searchable()), a Yes / No select (boolean()), a toggle switch (toggle()) or a date range (dateRange()); every value is checked again on the server. The search and the active filters stay in the URL, so a filtered board can be bookmarked and shared. Each column loads its cards in pages as you scroll.
Read more: Filters.
Configuration
$board
->reorderable('sort') // users order cards inside a column
->sortBy('due_at') // default order (a column can override it)
->poll('30s') // pick up other people's changes
->focusMode(false) // keep the panel's sidebar on the board page
->key('deals'); // where the browser remembers folded and hidden columns

The board sits on the page like a Filament table: a container with a toolbar whose controls, filters and menus are Filament's own components, styled by the panel's theme. The stylesheet is plain CSS on Filament's colour variables and follows dark mode; restyle the rest with the --ps-kanban-* variables on .ps-kanban (--ps-kanban-ring, --ps-kanban-col-width, --ps-kanban-card-bg, --ps-kanban-radius, …), the scheme Gantt and Spreadsheet share, prefixed so they win over the plugin's stylesheet (.fi-body .ps-kanban { … }).
Read more: Configuration, the full fluent API, and Styling.
Documentation
The same pages live in the docs/ directory of this repository.
Testing
composer test
Changelog
See the changelog.
Security vulnerabilities
Please e-mail support@packstub.dev rather than opening a public issue.
Credits
License
MIT. See the license file.
Related Packages
A fast Gantt chart and resource timeline for Filament: any Eloquent query with a...
A drag-and-drop visual page builder for Filament, storing content as ordered typ...
Star ratings for Filament v4 and v5: a form field, table column, infolist entry...
Edit hundreds of Eloquent records like a spreadsheet inside Filament: an inline-...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v0.2.0 | ^8.3 | ^12.0 || | MIT | |
| v0.1.0 | ^8.3 | ^12.0 || | MIT |
Pre-releases (1)
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v1.0.0-beta.1 | ^8.3 | ^12.0 || | MIT |
