It's time to make our first resource - Customer.
In this lesson, we will:
- Create DB structure for Customers: Model/Migration
- Create Factories/Seeds for testing data
- Generate Filament Resource directly from the DB structure
- Hide the
deleted_at
column from the table - Merge
first_name
andlast_name
into one table column
We will have the following fields in our Customer resource:
-
id
-
first_name
-
last_name
-
email
-
phone_number
-
description
-
timestamps
-
soft deletes
Creating Customer Database
Our first step is to create our Database table and Model:
Migration
Schema::create('customers', function (Blueprint $table) { $table->id(); $table->string('first_name')->nullable(); $table->string('last_name')->nullable(); $table->string('email')->nullable(); $table->string('phone_number')->nullable(); $table->text('description')->nullable(); $table->timestamps(); $table->softDeletes();});
As you can see, we have made all of our fields nullable. We don't know if the customer will have all these fields or just some. So, we leave some room for flexibility for our users.
Now, let's create the Model:
app/Models/Customer.php
use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\SoftDeletes; class Customer extends Model{ use SoftDeletes; use HasFactory; protected $fillable = [ 'first_name', 'last_name', 'email', 'phone_number', 'description', ];}
Since we use the HasFactory
trait, we should create a factory for our Customer model. It will be helpful for testing purposes!
php artisan make:factory CustomerFactory
Then, we will add the fields to our factory:
database/factories/CustomerFactory.php
use App\Models\Customer;use Illuminate\Database\Eloquent\Factories\Factory; /** * @extends Factory<Customer> */class CustomerFactory extends Factory{ protected $model = Customer::class; /** * Define the model's default state. * * @return array<string, mixed> */ public function definition(): array { return [ 'first_name' => $this->faker->firstName(), 'last_name' => $this->faker->lastName(), 'email' => $this->faker->unique()->safeEmail(), 'phone_number' => $this->faker->phoneNumber(), 'description' => $this->faker->text(), ]; }}
Now, we can create a seeder for our Customer model:
database/seeders/DatabaseSeeder.php
use App\Models\Customer; // ... public function run(): void{ // ... Customer::factory() ->count(10) ->create();}
We can test our seeder by running:
php artisan migrate:fresh --seed
This command should clear our database, migrate it from scratch, and seed it with our defined seeders. Once it's done, we should be able to see our customers in the database:
This should be enough for us to test our Filament resource. Let's create it!
Creating Customer Resource
To create the base resource, we can use the Filament generator:
php artisan make:filament-resource Customer --generate
After running this command, we should see a few new files in our project:
These files contain all the necessary code to create a resource in Filament, meaning that we can open our browser and visit our Customers page:
How cool is that? We just created a model, ran a single command, and Filament generated a resource for us! But let's dive deeper into what we just created and modify some things to make it more useful:
Modifying Customer Resource
Our primary focus should be app/Filament/Resources/CustomerResource.php
as this file is responsible for Table and Form generation. Let's take a look at it:
app/Filament/Resources/CustomerResource.php
use App\Filament\Resources\CustomerResource\Pages;use App\Filament\Resources\CustomerResource\RelationManagers;use App\Models\Customer;use Filament\Forms;use Filament\Forms\Form;use Filament\Resources\Resource;use Filament\Tables;use Filament\Tables\Table;use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\SoftDeletingScope; class CustomerResource extends Resource{ // ... public static function form(Form $form): Form { // This is where we define what fields we want to have in our form return $form ->schema([ Forms\Components\TextInput::make('first_name') ->maxLength(255), Forms\Components\TextInput::make('last_name') ->maxLength(255), Forms\Components\TextInput::make('email') ->email() ->maxLength(255), Forms\Components\TextInput::make('phone_number') ->tel() ->maxLength(255), Forms\Components\Textarea::make('description') ->maxLength(65535) ->columnSpanFull(), ]); } public static function table(Table $table): Table { // This is where we define our table columns, filters, actions, and any other table-related things return $table ->columns([ Tables\Columns\TextColumn::make('first_name') ->searchable(), Tables\Columns\TextColumn::make('last_name') ->searchable(), Tables\Columns\TextColumn::make('email') ->searchable(), Tables\Columns\TextColumn::make('phone_number') ->searchable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('deleted_at') ->dateTime() ->sortable(), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } // ...}
Table Modifications
But how can we modify this? Well, let's start with something simple - our Customers table has a deleted_at
column that we want to hide:
To hide it, we can borrow some code from the created_at
column like so:
app/Filament/Resources/CustomerResource.php
use App\Filament\Resources\CustomerResource\Pages;use App\Filament\Resources\CustomerResource\RelationManagers;use App\Models\Customer;use Filament\Forms;use Filament\Forms\Form;use Filament\Resources\Resource;use Filament\Tables;use Filament\Tables\Table;use Illuminate\Database\Eloquent\Builder;use Illuminate\Database\Eloquent\SoftDeletingScope; class CustomerResource extends Resource{ // ... public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('first_name') ->searchable(), Tables\Columns\TextColumn::make('last_name') ->searchable(), Tables\Columns\TextColumn::make('email') ->searchable(), Tables\Columns\TextColumn::make('phone_number') ->searchable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('deleted_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } // ...}
Now, if we refresh our page, we should see that the column is hidden:
But that's not all! We still want a Full Name column in our table, not just the first_name
and last_name
columns. To do that, we need to create a new column, Name
and remove the first_name
and last_name
columns:
app/Filament/Resources/CustomerResource.php
// ... public static function table(Table $table): Table{ return $table ->columns([ Tables\Columns\TextColumn::make('first_name') // We are setting the column label to "Name" ->label('Name') // This function allows us to format the column value // In this case, we are concatenating first_name and last_name ->formatStateUsing(function ($record) { return $record->first_name . ' ' . $record->last_name; }) // This function allows us to inform Filament that this column is searchable // And also define in which columns the search should be performed // In this case - first_name and last_name columns ->searchable(['first_name', 'last_name']), Tables\Columns\TextColumn::make('first_name') ->searchable(), Tables\Columns\TextColumn::make('last_name') ->searchable(), // ... ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]);}// ...
And this is how our table looks now:
And the search also works if we search for a last name:
Or even if we search for a full name:
Form Modifications
Next, let's check that our form is working as expected, too. Open the form and try to create a new customer:
As you can see, even if we enter a valid phone number - we still get an error. This is because we have a validation rule for our phone number field:
app/Filament/Resources/CustomerResource.php
public static function form(Form $form): Form{ return $form ->schema([ // ... Forms\Components\TextInput::make('phone_number') // This is the validation rule that causes the error. We will remove it for now ->tel() ->maxLength(255), // ... ]);}
Now, once the rule is removed, we can create a new customer:
As you can see, we have successfully created a new customer!
This is it! You have successfully created your first resource in Filament! Next, we will create a new resource - Lead Sources.
Why removing the validation rule for phone. Does this means that we allow user to input any data without validation? I guess the phone validation is important from my perspective.
Phone validation comes with it's own set of rules and formatting. It does not really match other common formats, so we removed it and left for everyone to decide on it :)
Okay, can we explore validation rules with filament in upcoming course on filament. Thanks
Will take a look at what we can do here!
Most likely we will have a section on custom rule creation to fit fields like phone number in
Great!
I have a suggestion for the phone validation
'phone' => 'required|regex:/^([0-9\s-+()]*)$/|min:10',
'mobile' => 'regex:/^([0-9\s-+()]*)$/|min:10|nullable',
This is what I normaly use.
Phone validation is tricky, as each country might have different rules for formatting. In any case, if it works - it works!
As of Filament 3.0.94 the error with the phone number is no more I am not getting the error as of today 11/12/2023 doing a restart with github
please add the command for making a migration and a model in the begining of the leson. php artisan make:model Customer -m
for new people coming to the laravel this can help
Hi, not sure if the migration/model making command will help here a lot. We assume that people who learn filament already know at least the basics of Laravel.
Will think about it, but no promises that it will be updated
I agree with this, as for me who is paying 26$ per month i want everything to be clear.
I support it, I agree with it
Hi, what is the best way to extend a model with created_by, edited_by and deleted_by. I think it is important to know who edited the record. Thanks for the help.
Hello Thomas,
This is Model Activity logs . The spatie/laravel-activitylog package provides easy to use functions to log the activities of the users of your app. It can also automatically log model events. All activity will be stored in the activity_log table.
Check this plugin here: https://spatie.be/docs/laravel-activitylog/v4/introduction
Hi, thanks for your replay. I would like to extend my database with user_id from the auth-user who changed the record in created_by, edited_by and delted_by
Check the package database structure.
Hello, I used this code in the model and it is working well by creating und editing the record, but not by deleting (softdelete).
Sorry, I don't know I can formating the code well
public static function booted(): void
{
} }
Hi, I would suggest you to use the spatie activity logs package as it tracks all those actions automatically and more! But if you don't want to do that, you have to remember that deleting works differently. It does not call the ->save() as you would think, so you have to manually add:
$user->save()
After you have set the ID.
Good morning Modestas, $user->save() works. Thanks for the support and thanks for the recommendation. I will take a look at the package
Hi.
Does anyone know what version laravel and filament is being used here. I currently have laravel 11 with filament 3.2 some of the code in the files are different, so its hard to follow.
Does it matter if I am on the latest versions?
Hi, this was created with laravel 10 and filament 3.
If you are learning - it does not matter to be on the latest version!
Im using laravel 11 and it works!