Back to Course |
Filament 3 From Scratch: Practical Course

Generate Simple Resources and hasMany Count

So, we have a belongsTo relationship from Product to Category. Now, what if we want to manage Categories? In this 2-in-1 lesson, we will create that second Filament Resource in a simple "modal" way. Also, we will see how to show the number of products for each category in the table.


Simple Resource for Categories

Similarly to the products, we could generate another Filament Resource for the Category Model:

php artisan make:filament-resource Category

But I want to show you two additional options.

First, not every CRUD needs all the create/edit pages. If it's simply one field of "categories.name", like we have here, we could use modals to manage the information. The UX would be quicker, right?

So:

php artisan make:filament-resource Category --simple

And wait, don't run this command yet. Another trick is incoming.

If you followed the first Resource example, you would think you need to add the "name" field as a table column and as a form field later, right?

But what if Filament could generate it for us, guessing the fields from our database schema?

Let's add another option, --generate.

php artisan make:filament-resource Category --simple --generate

We refresh the page and see the new menu item "Categories", with the column Name already shown in the table. And I haven't written a single line of code for it!

Here's the Resource code generated by Filament:

app/Filament/Resources/CategoryResource.php

class CategoryResource extends Resource
{
protected static ?string $model = Category::class;
 
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
 
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
]);
}
 
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
//
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
])
->emptyStateActions([
Tables\Actions\CreateAction::make(),
]);
}
 
public static function getPages(): array
{
return [
'index' => Pages\ManageCategories::route('/'),
];
}
}

As you can see, the form has one column, "name". It has been generated with the help of the doctrine/dbal package. But use it with caution, especially for DB tables with many columns: it is pretty accurate but not 100% reliable: it wouldn't handle ENUM fields, for example.

Also, you see that in the table, Filament suggests showing not only the name but also the created_at/updated_at timestamps. However, they are hidden from the table by default. The user can show them with the ->toggleable(isToggledHiddenByDefault: true) method. Here's how it looks visually:

Next, if you click on Edit, you will not see a separate page, but rather a modal:

This is achieved by just removing create/edit pages from this method:

public static function getPages(): array
{
return [
'index' => Pages\ManageCategories::route('/'),
 
// No create/edit pages here
];
}

Also, in the /Pages subfolder of the resource, you wouldn't see the CreateCategory/EditCategory/ListCategories files. Instead, there's only one file to manage categories:

app/Filament/Resources/CategoryResources/Pages/ManageCategories.php:

namespace App\Filament\Resources\CategoryResource\Pages;
 
use App\Filament\Resources\CategoryResource;
use Filament\Actions;
use Filament\Resources\Pages\ManageRecords;
 
class ManageCategories extends ManageRecords
{
protected static string $resource = CategoryResource::class;
 
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

So this is how you create a simple Filament Resource and generate the form/table for it.


HasMany: Showing Number of Products

Let's add one more column to the Categories table and show the number of products for each category.

First, let's define the relationship:

app/Models/Category.php:

class Category extends Model
{
public function products()
{
return $this->hasMany(Product::class);
}
}

And then let's add a column like this:

return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('products_count')
->counts('products'),
// ... other columns

Yes, that's it! Here's the result:

So you need to specify the relationship name in the counts() method and what field to show as [relationship]_count, as that's what Laravel uses in the withCount() method that is used under the hood.

But you don't need to call withCount() anywhere manually. Filament will do that for you.

On a database level, this query with a subquery would be executed:

select `categories`.*,
(select count(*) from `products`
where `categories`.`id` = `products`.`category_id`) as `products_count`
from `categories`
order by `categories`.`id` asc
limit 10
offset 0

The final thing in this lesson: let's change the column name from the default "Products count" to just "Products". For that, you just add another method to the chain (I hope you're getting used to this "chaining" by now!), called ->label().

Tables\Columns\TextColumn::make('products_count')
->label('Products')
->counts('products'),

And this is the final result of this lesson!