Back to Course |
Creating CRM with Filament 3: Step-By-Step

Creating Customers Resource

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 and last_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.