Filament Tables allow you to implement tabs easily. But did you know you can specify the tab names/values dynamically from the database, also controlling the order in which they appear? Let's take a look.
In this example, we have the following database table schema and Models.
database/migrations/XXXXXX_create_tiers_table.php
Schema::create('tiers', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('order_column'); $table->timestamps();});
database/migrations/XXXXXX_create_customers_table.php
use App\Models\Tier; // ... Schema::create('customers', function (Blueprint $table) { $table->id(); $table->foreignIdFor(Tier::class)->nullable()->constrained(); $table->string('full_name'); $table->timestamps();});
app/Models/Tier.php
class Tier extends Model{ // ... protected $guarded = []; public function customers(): HasMany { return $this->hasMany(Customer::class); }}
app/Models/Customer.php
class Customer extends Model{ // ... protected $guarded = []; public function tier(): BelongsTo { return $this->belongsTo(Tier::class); }}
To add tabs, we must define the getTabs()
method to your List<Resource>.php
file. In this case, it is ListCustomers
.
app/Filament/Resources/CustomerResource/Pages/ListCustomers.php
namespace App\Filament\Resources\CustomerResource\Pages; use App\Filament\Resources\CustomerResource;use App\Models\Tier; use Filament\Actions;use Filament\Resources\Components\Tab; use Filament\Resources\Pages\ListRecords; class ListCustomers extends ListRecords{ protected static string $resource = CustomerResource::class; protected function getHeaderActions(): array { return [ Actions\CreateAction::make(), ]; } public function getTabs(): array { $tabs = ['all' => Tab::make('All')->badge($this->getModel()::count())]; $tiers = Tier::orderBy('order_column', 'asc') ->withCount('customers') ->get(); foreach ($tiers as $tier) { $name = $tier->name; $slug = str($name)->slug()->toString(); $tabs[$slug] = Tab::make($name) ->badge($tier->customers_count) ->modifyQueryUsing(function ($query) use ($tier) { return $query->where('tier_id', $tier->id); }); } return $tabs; }}
When returning the tabs array, the index of that array is a value of the activeTab
URL parameter. So, we can generate a slug from the Tier name instead of just having a number.
//... /admin/customers?activeTab=platinum$slug = str($name)->slug()->toString();
Tabs can have badges. The badge()
method also accepts closures to display data depending on your custom logic. In our case, we show the Customer count in that Tier.
->badge($tier->customers_count)
Eloquent Query is modified with the ->modifyQueryUsing()
method to filter records. Here, we extend the query to select Customers with the tier_id
of the activeTab
.
->modifyQueryUsing(function ($query) use ($tier) { return $query->where('tier_id', $tier->id);});
Tier tabs are sorted by the order_column
column in ascending order.
$tiers = Tier::orderBy('order_column', 'asc')
You can change the order of tabs by modifying the order_column
value on the EditTier
page.
Optionally, you can reorder rows directly on the ListTiers
page by adding these methods to the TierResource@table
method.
->defaultSort('order_column')->reorderable('order_column')
Implemented Tier and Customer Resources are as follows.
app/Filament/Resources/TierResource.php
use Filament\Forms\Components\TextInput; use Filament\Tables\Columns\TextColumn; class TierResource extends Resource{ // ... public static function form(Form $form): Form { return $form ->schema([ TextInput::make('name') ->required(), TextInput::make('order_column') ->required() ->numeric(), ]); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('order_column'), TextColumn::make('name'), TextColumn::make('customers_count') ->counts('customers') ->badge(), ]) ->defaultSort('order_column') ->reorderable('order_column') // ... }}
app/Filament/Resources/CustomerResource.php
use App\Models\Tier; use Filament\Forms\Components\Select;use Filament\Forms\Components\TextInput;use Filament\Tables\Columns\TextColumn; class CustomerResource extends Resource{ // ... public static function form(Form $form): Form { return $form ->schema([ TextInput::make('full_name') ->required(), Select::make('tier_id') ->label('Tier') ->options(Tier::pluck('name', 'id')), ]); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('full_name'), TextColumn::make('tier.name') ->badge(), ]) // ... }}
Congratulations! You've successfully implemented dynamic Tabs for your Filament Table.
If you want more Filament examples, you can find more real-life projects on our FilamentExamples.com.
This is a great tutorial including Tabs. Is anyone aware of a solution where each tab can display only the needed table columns? My example would be to have a tab for income, another of expense, and a third for a complete list of both. I don't necessary need some of the colums listed in all three options. I apologize if the solution has already been addressed but I havent come accross it yet. Thank you and please keep up the good work!
Tabs are accessible as URL parameter, so you can add
->hidden()
with a condition for all the tabsThank you for point me in the right direction.
Hello! Thank you for the great tutorial! It works perfectly if I put the getTabs in the ListCustomers. But If I put the same tabs and the same table in the custom page they work but the tabs don't affect the query of the table. Can you please point me towards the right direction?
got it. I was lurking through the ListRecords and found ->modifyQueryUsing($this->modifyQueryWithActiveTab(...)) applied it to the $table and voila
Hi ! Could you show me the demo code?
My approach
It works fine, but I need to click the button twice to get it to work. It seems to be cached. But I have found a good solution.
Usually you have multiple filters on the table so the number in the badge should be updated accordingly. Although it seems trivial it is not.
You can use a helper function to remove the badge's where statement from the table query to show the correct numbers (reflecting the set filters of the table):
the helper function look something like this ...