Installation:
composer require alessandro-nuunes/filament-kanban
php artisan filament-kanban:install
Add @source directive to resources/css/filament/admin/theme.css (as shown in README) and rebuild assets.
Generate a Kanban Page:
php artisan make:filament-kanban TicketsKanban --resource=TaskResource --model=Task --panel=admin
This creates a scaffolded Kanban page in app/Filament/Admin/Resources/Tasks/Pages/TicketsKanban.php.
Register the Page:
Add the generated page to your resource’s getPages() method:
public static function getPages(): array {
return [
'index' => Pages\ListTasks::route('/'),
'kanban' => Pages\TicketsKanban::route('/kanban'),
'view' => Pages\ViewTask::route('/{record}'),
];
}
First Run:
Visit /admin/tasks/kanban in your Filament panel. The board will render empty unless you configure columns and data.
Define an Enum (if not already present):
// app/Models/Task.php
use Filament\Support\Enums\Status;
public static function getStatusOptions(): array {
return Status::options([
'todo' => 'To Do',
'in-progress' => 'In Progress',
'done' => 'Done',
]);
}
Configure the Kanban Page:
Override getColumns() and getRecords() in TicketsKanban.php:
public function getColumns(): array {
return [
'todo' => 'To Do',
'in-progress' => 'In Progress',
'done' => 'Done',
];
}
public function getRecords(): array {
return Task::query()
->orderBy('status')
->get()
->toArray();
}
Map Records to Columns:
Override getColumnForRecord() to assign records to columns:
public function getColumnForRecord(Task $record): string {
return $record->status;
}
Now, tasks will appear in their respective columns based on the status field.
Use getColumns() to define columns dynamically (e.g., fetch from a database or API):
public function getColumns(): array {
return Cache::remember('kanban_columns', now()->addHours(1), function () {
return Status::getOptions();
});
}
Override getCard() to modify how records appear as cards:
public function getCard(Task $record): array {
return [
'title' => $record->title,
'description' => $record->description,
'avatar' => $record->assigned_user->avatar,
'badge' => $record->priority,
'actions' => [
Action::make('edit')
->url(fn (Task $record) => $this->getResource()::getUrl('edit', ['record' => $record])),
],
];
}
Listen for column changes via getColumnChanged():
protected function getColumnChanged(): string {
return 'status';
}
protected function handleColumnChanged(Task $record, string $newColumn): void {
$record->update(['status' => $newColumn]);
}
Add Filament actions (e.g., bulk actions) to the Kanban page:
public function getHeaderActions(): array {
return [
Action::make('create')
->url(fn () => $this->getResource()::getUrl('create')),
Action::make('export')
->icon('heroicon-o-arrow-down-tray')
->url(fn () => route('filament.admin.tasks.export')),
];
}
Use Filament’s built-in filtering or add custom logic:
public function getRecords(): array {
return Task::query()
->when($this->getFilters(), fn ($query) => $this->applyFilters($query))
->get()
->toArray();
}
getTableRecords() or getTableColumns() for consistency.public function getFilters(): array {
return $this->getResource()->getTableFilters();
}
public function getColumnForRecord($record): string {
return $record->status_column ?? 'default';
}
getRecords() to fetch from an external API:
public function getRecords(): array {
return Http::get('https://api.example.com/tasks')->json();
}
dark: variants for consistency:
.kanban-card {
@apply bg-white dark:bg-gray-800;
}
Missing @source Directive:
@source is added to theme.css and run npm run build.Column Assignment Conflicts:
getColumnForRecord() returns a key matching getColumns().dd($record->status) to check the status value.Drag-and-Drop Not Working:
getColumnChanged() returns the correct field name (e.g., 'status').Enum Mismatch:
getStatusOptions() matches the Kanban page’s getColumns().Caching Issues:
php artisan filament:cache-reset
Log Column Assignments:
Add temporary logging in getColumnForRecord():
public function getColumnForRecord(Task $record): string {
\Log::debug('Assigning record ID ' . $record->id . ' to column: ' . $record->status);
return $record->status;
}
Inspect API Requests:
Use Laravel’s debug bar or Chrome DevTools to verify data fetched in getRecords().
Check Tailwind Conflicts: If styles break, inspect the generated HTML to ensure no duplicate or conflicting classes exist.
Default Column:
If a record’s column isn’t found, it defaults to 'default'. Override getDefaultColumn():
public function getDefaultColumn(): string {
return 'todo';
}
Empty State: Customize the empty state message:
public function getEmptyState(): string {
return 'No tasks found. Create one to get started!';
}
Card Height:
Adjust card height via Tailwind classes in getCard():
public function getCard(Task $record): array {
return [
'title' => $record->title,
'classes' => 'h-32', // Custom height
];
}
Custom Card Components: Replace the default card view by publishing and overriding:
php artisan vendor:publish --tag=filament-kanban-views
Then modify resources/views/kanban/card.blade.php.
Column Headers:
Customize column headers by overriding getColumnHeader():
public function getColumnHeader(string $column): array {
return [
'title' => ucfirst($column),
'icon' => match ($column) {
'done' => 'heroicon-o-check-circle',
default => 'heroicon-o-rectangle-stack',
},
];
}
Event Listeners:
Listen for column changes globally by registering a listener for FilamentKanban\Events\ColumnChanged:
use FilamentK
How can I help you explore Laravel packages today?