codewithdennis/filament-select-tree
Dynamic select tree field for Laravel Filament that renders hierarchical dropdowns from relationships or custom queries. Supports BelongsTo and BelongsToMany, parent/child attributes, and customizable parent/child query scopes with optional strict root behavior.
Installation:
composer require codewithdennis/filament-select-tree:4.x
php artisan filament:assets
Basic Usage (Relationship-Based):
For a BelongsTo relationship (single selection):
use CodeWithDennis\FilamentSelectTree\SelectTree;
SelectTree::make('category_id')
->relationship('category', 'name', 'parent_id');
For a BelongsToMany relationship (multi-select):
SelectTree::make('categories')
->relationship('categories', 'name', 'parent_id');
Non-Relationship Usage:
SelectTree::make('category_id')
->query(fn() => Category::query(), 'name', 'parent_id');
Replace a standard Select or MultiSelect field in a Filament form/resource with hierarchical data (e.g., product categories). Example:
use Filament\Forms\Components\SelectTree;
SelectTree::make('category_id')
->label('Product Category')
->relationship('category', 'name', 'parent_id')
->required();
SelectTree::make('parent_category_id')
->relationship('parentCategory', 'title', 'parent_id')
->searchable()
->placeholder('Select a parent category');
SelectTree::make('categories')
->relationship('categories', 'name', 'parent_id')
->multiple()
->enableBranchNode()
->withCount();
Filters\Filter::make('category_filter')
->form([
SelectTree::make('categories')
->relationship('categories', 'name', 'parent_id')
->independent(false)
->enableBranchNode(),
])
->query(function (Builder $query, array $data) {
return $query->when($data['categories'], fn($q) =>
$q->whereHas('categories', fn($q) => $q->whereIn('id', $data['categories']))
);
});
Use getTreeUsing for custom tree structures:
SelectTree::make('custom_tree')
->getTreeUsing(function () {
return Category::with('children')
->get()
->map(fn($category) => [
'name' => $category->name,
'value' => $category->id,
'children' => $category->children->map(fn($child) => [
'name' => $child->name,
'value' => $child->id,
])->toArray(),
]);
});
Add custom options to the tree (e.g., "All Categories"):
SelectTree::make('categories')
->relationship('categories', 'name', 'parent_id')
->prepend([
'name' => 'All Categories',
'value' => 'all',
'parent' => null,
]);
independent(false) for dependent fields (e.g., child categories depend on parent selection).storeResults() to cache query results and withTrashed() to include soft-deleted items if needed.->placeholder(__('Select an option'))
->emptyLabel(__('No results found'))
Strict Parent Filtering:
->strictNullParentRootNodes();
parent_id values align with your database schema (e.g., null vs. -1 via parentNullValue(-1)).Closure Evaluation Timing:
prepend()/append() are deferred. Ensure they return the expected structure:
->prepend(fn() => ['name' => 'Dynamic Option', 'value' => 1])
Key Mismatches:
withKey('code')), ensure the stored value matches the relationship’s foreign key.Search Behavior:
getTreeUsing with custom logic.dd() in the closure to inspect results:
->query(fn() => dd(Category::query()->get()))
multiple() is set correctly for BelongsToMany relationships.php artisan filament:cache-clear) if styles behave unexpectedly.Custom Tree Logic:
Override tree generation with getTreeUsing for complex hierarchies (e.g., multi-tenancy):
->getTreeUsing(function () {
return auth()->user()->categories->toTreeArray();
});
Dynamic Disabled/Hidden Options:
Use disabledOptions/hiddenOptions with stored results:
->storeResults()
->disabledOptions(fn($state, $component) => [
$component->getResults()->where('is_active', false)->pluck('id')
]);
Event Listeners:
Attach JavaScript events (e.g., x-load) for dynamic updates:
->extraAttributes(['x-load' => 'loadTreeData'])
defaultOpenLevel(2) to auto-expand the tree to a specific depth.isGroupedValue() to return parent IDs when all children are selected.staticList() to prevent dropdown overlap in dense forms.top/bottom) for consistent placement:
->direction('top')
How can I help you explore Laravel packages today?