Back to Course |
Filament 3 From Scratch: Practical Course

Select Dropdown with/without belongsTo

In this lesson, let's review one of the most popular field types used in forms: dropdown selects. The data for the options may or may not come from another DB table as a relationship.

We will add two fields to our Products form:

  • select to choose product status: "in stock", "sold out", "coming soon"
  • select to choose product category: from another Category model

Simple Select Dropdown

Let's say that product may have one of the defined statuses, but we don't want to save them in a separate table.

Instead, we have an Enum field in the database:

Schema::table('products', function (Blueprint $table) {
$table->enum('status',
['in stock', 'sold out', 'coming soon'])
->default('in stock');
});

Let's make this field fillable in the Product Model.

app/Models/Product.php:

class Product extends Model
{
use HasFactory;
 
protected $fillable = ['name', 'price', 'status'];
}

And then, we can show the value in the table, just as a simple column:

app/Filament/Resources/ProductResource.php:

return $table
->columns([
Tables\Columns\TextColumn::make('name')
->sortable(),
Tables\Columns\TextColumn::make('price')
->sortable()
->money('usd')
->getStateUsing(function (Product $record): float {
return $record->price / 100;
}),
Tables\Columns\TextColumn::make('status'),
])

In the form, we add that field as a Select::make(), with options as an array of key-value pairs:

app/Filament/Resources/ProductResource.php:

return $form
->schema([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('price')->required(),
Forms\Components\Select::make('status')
->options([
'in stock' => 'in stock',
'sold out' => 'sold out',
'coming soon' => 'coming soon',
]),
]);

This is the result in the form:

And this is the result in the table:

Notice: of course, the repeating values of the options could be optimized and loaded from elsewhere: you could store them in the Model or create a PHP Enum class. But that's outside of the scope of this tutorial. My goal here is just to show you how Select fields work with Filament.


Radio Instead of Select?

Another visual option, if you have a set of values, is to use Radio and not Select.

For that, we don't change anything in the DB, and in the Filament Table, we just change one word in the Form definition:

app/Filament/Resources/ProductResource.php:

Forms\Components\Select::make('status')
Forms\Components\Radio::make('status')
->options([
'in stock' => 'in stock',
'sold out' => 'sold out',
'coming soon' => 'coming soon',
]),

And here's the visual result:

To me, personally, in most cases, the web user experience of a radio button is better than extra clicking on select to find out the possible option values.


Product Category: Select with belongsTo

Now, let's talk about relationships. What if you want to have Product Categories like this?

Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
 
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->foreignId('category_id')->nullable()->constrained();
});

For testing, I've added a few Categories behind the scenes:

And then, we define the relationship and add the fillable field:

app/Models/Product.php:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Product extends Model
{
// ...
 
protected $fillable = [
'name',
'price',
'status',
'category_id'
];
 
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}

Now, how do we get the relationship in the Table and the Form?

In the Form, you can just provide a relationship() method in the chain with two parameters:

  • Relationship method name: in our case, the method in the Model is called category()
  • Relationship field name to be visible as a dropdown option value: in our case, name from categories.name DB column

app/Filament/Resources/ProductResource.php:

return $form
->schema([
Forms\Components\TextInput::make('name')->required(),
Forms\Components\TextInput::make('price')->required(),
Forms\Components\Radio::make('status')
->options([
'in stock' => 'in stock',
'sold out' => 'sold out',
'coming soon' => 'coming soon',
]),
Forms\Components\Select::make('category_id')
->relationship('category', 'name'),

And that's it. Here's the updated form:

The value of category_id will be successfully saved in the database:

Now we need to show it in the table.

To do that, we add another TextColumn, and all we need to do to load the data from the relationship is to use a dot-notation name of relationship.column.

app/Filament/Resources/ProductResource.php:

return $table
->columns([
// ... other columns
Tables\Columns\TextColumn::make('status'),
Tables\Columns\TextColumn::make('category.name'),
])

That's it. The values are shown in the table!

And before you ask about N+1 query and eager loading: don't worry. Filament takes care of it automatically. You don't need to specify Product::with('category') anywhere.

If we install Laravel Debugbar and take a look at the queries, everything is fine: one query for the products and one query for the categories:


So yeah, this is how simple it is to deal with the belongsTo relationship fields in the forms/tables of Filament.