If you just wish to learn about Filament, please read their documentation and check their demo repo. It will be faster than following my post.
Intro
I have used quite a few admin or CRUD generator kind of Laravel packages for rapid development in the past. Filament is youngest among them but it has quite a lot of things that I like about.
- open-source and free (unlike Nova or Backpack)
- stay in Laravel ecosystem; TALL stack (unlike Orchid)
- good documentation and active community
- enough form fields to use for any type of project
- easy to use table with filter, sorting
- good support for Eloquent’s relationships
- come with navigation, pages, widgets, charts and notifications
- plenty of plugins (not hundreds or thousands, still pretty good amount)
- mainly it is easy to learn thanks to the documentation, demo project repo and existing plugins
So I have decided to use Filament for my future Laravel projects. This post is my journal/guide/log/notes of my learning and experimenting with Filament. My aim is to use Filament together with other packages I normally use. So my project will have other packages for APIs development, APIs integration, deployment, logging and so on. (I have trouble installing Laravel Module package so I have removed it for now. I will try to install it again and use Filament from Module in future.)
I have setup a Github repo mrlinnth/laravel-filament-starter for my project. If you are someone who prefer to read code instead of text, feel free to visit there instead. I update the README file with short to the point steps that I did.
I am gonna skip the followings in this post
- Setting up local environment for Laravel project on different OS
- Setting up VS Code for Laravel project on different OS
- Installing and setting up Laravel packages that I usually use
I am not even going to write about installing and setting up Filament. If you are using my repo then it’s already installed. I plan to write separate posts for those how to setup topics in future.
If you want to read and follow this post, you will need to
- clone my repo mrlinnth/laravel-filament-starter
- checkout related branch for each step
- update .env with your DB info
- run
composer install - run
npm install - run
php artisan migrate - run
php artisan optimize
Step 1 - First CRUD
Branch : steps/01-first-crud
First I want to learn how to develop CRUD pages. I also want to see how Filament handles relationships. I decided to develop a superherodb.com inspired project since I am bored with Blog project.
Requirements
- User can create and update heroes.
- A hero can have many skills.
- A hero can belong to teams.
- A hero can be a leader of a team.
When I have requirements, I decide a DB structure

Project Initiate
Generate model, factory, migration and seed files
php artisan make:model -fms Hero
php artisan make:model -fms Skill
php artisan make:model -fms Team
Generate pivot table migration for hero_team
php artisan make:migration create_hero_team_table
Update the migration files
Schema::create(‘heroes’, function (Blueprint $table) { $table->id(); $table->string(‘name’); // text $table->text(‘history’); // textarea $table->string(‘species’); // dropdown $table->string(‘gender’); // radio button $table->integer(‘age’); // number $table->string(‘eye_color’); // color picker $table->json(‘traits’); // group of checkbox $table->timestamps(); });
Schema::create(‘skills’, function (Blueprint $table) { $table->id(); $table->string(‘name’); // text $table->foreignId(‘hero_id’)->constrained(); // dropdown $table->integer(‘power’)->default(50); // number $table->timestamps(); });
Schema::create(‘teams’, function (Blueprint $table) { $table->id(); $table->string(‘name’); // text $table->text(‘story’); // wysiwyg or rich text editor $table->foreignId(‘leader_id’) ->constrained(‘heroes’) ->onUpdate(‘cascade’) ->onDelete(‘cascade’); $table->timestamps(); });
Schema::create(‘hero_team’, function (Blueprint $table) { $table->id(); $table->foreignId(‘hero_id’)->constrained(); $table->foreignId(‘team_id’)->constrained(); $table->timestamps(); });
I write down the form field type so that I don't have to think when I setup CRUD form later.Run Migrate
php artisan migrate
Hero has species and traits attributes. They will be predefined but instead of hard coding them directly at form fields we can use Enum class. Spatie Laravel Enum package is very useful.
Generate and update Enum classes
php artisan make:enum SpeciesEnum
/**
* @method static self human()
* @method static self alien()
* @method static self god()
* @method static self robot()
* @method static self mutant()
* @method static self other()
*/
final class SpeciesEnum extends Enum
{
}
php artisan make:enum TraitsEnum
/** * @method static self intelligence() * @method static self strength() * @method static self speed() * @method static self durability() * @method static self power() * @method static self combat() */ final class TraitsEnum extends Enum { }
**Update models with casts and relationship methods**Hero.php
SpeciesEnum::class, 'traits' => TraitsEnum::class.':collection,nullable', ]; /** * The attributes that aren't mass assignable. * * @var array */ protected $guarded = []; /** * Get the skills of the hero. */ public function skills() { return $this->hasMany(Skill::class); } /** * The teams that belong to the hero. */ public function teams() { return $this->belongsToMany(Team::class); } /** * Get the teams the hero is leader of. */ public function leaderTeams() { return $this->hasMany(Team::class, 'leader_id'); } } Skill.php belongsTo(Hero::class); } } Team.php belongsToMany(Hero::class); } /** * Get the leader that owns the team. */ public function leader() { return $this->belongsTo(Hero::class, 'leader_id'); } } ## Generate CRUD **Filament resources** Filament provide 3 options for generating CRUD. Filament address entity/model as resource so I will follow. 1. default : You will need to define the form fields for create and edit pages and table columns for listing page. Create and edit form is separate page. Delete button is not showing at listing page. 2. simple : You still need to define the form fields and table columns. Create and edit form is a modal box now. Delete button exists at listing page. Good for resource with 2,3 small attributes. Not suitable for resource with many attributes or long text attribute. 3. generate : Form fields and table columns are auto generated. The rest is same as default. Useful if majority of the attributes are common type (eg: text, number). You won't need to create everything from scratch. You just need to update the generated code as you want. You need doctrine/dbal package for this. `php artisan make:filament-resource Hero` public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name')->required(), Forms\Components\Select::make('species') ->options(SpeciesEnum::toArray()), Forms\Components\Radio::make('gender') ->options([ 'male' => 'Male', 'female' => 'Female', 'other' => 'Other' ]), Forms\Components\CheckboxList::make('traits') ->options(TraitsEnum::toArray()) ->columns(2), Forms\Components\TextInput::make('age')->numeric(), Forms\Components\ColorPicker::make('eye_color'), Forms\Components\Textarea::make('history')->columnSpan('full'), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('species'), Tables\Columns\TextColumn::make('gender'), Tables\Columns\TextColumn::make('age'), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } `php artisan make:filament-resource Skill --simple` public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name')->required(), Forms\Components\Select::make('hero_id')->relationship('hero', 'name'), Forms\Components\TextInput::make('power')->numeric(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('hero.name'), Tables\Columns\TextColumn::make('power'), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } `php artisan make:filament-resource Team --generate` public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name')->required(), Forms\Components\Select::make('leader_id')->relationship('leader', 'name'), Forms\Components\RichEditor::make('story')->columnSpan('full'), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('leader.name'), ]) ->filters([ // ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } - I update the generated fields and columns as I like. - I use `columnSpan('full')` method for the long text fields. - Skill and Team resources have hero as select drop down for the relationship. - Skill table actions() method has DeleteAction. So if we add it to other two resource, they will also show delete action at listing page. - Hero resource has radio buttons, checkbox group, color picker. - Team has rich text (wysiwyg) editor. You should be able to create, edit, delete the resources. But you will need to create user to login and access Filament admin dashboard. Or use my dummy data it already has test user account. **Create user** `php artisan make:filament-user` and enter details Visit yourdomain/admin and login. You should see this.  **Dummy data** I use Laravel's Factory and Seeder classes. I update the HeroFactory, SkillFactory and TeamFactory. I update TeamSeeder and SkillSeeder. Since TeamFactory will also generate Heroes, I don't use HeroSeeder. I update DatabaseSeeder with test user and call TeamSeeder and SkillSeeder. Run `php artisan migrate:fresh --seed` to clear the database, run migrations and seeders. Visit yourdomain/admin and login - username : [admin@mail.com](mailto:admin@mail.com) - password : password You will see dummy data records for heroes, skills and teams.  ## File upload Let's upload a photo for hero resource. With the help of Spatie Media Library package and Filament Media Library plugin package, we do not need to update hero table structure or anything. To add file upload field to hero create/edit form, update `form()` method `$form->schema` parameter array in `HeroResource.php` with following Forms\Components\SpatieMediaLibraryFileUpload::make('photo') ->collection('hero'), It is better to define `collection()` parameter, since it is how we will associate the uploaded file with our resource. To show the image in table column, update `table()` method `$table->columns` parameter array in `HeroResource.php` with following Tables\Columns\SpatieMediaLibraryImageColumn::make('photo') ->collection('hero'),   ## Relationships **One to many relationship** Hero and skill have one to many relationship. Since a skill can only belong to one hero, we need to select hero when a skill is created. Relationship name is `hero` and foreign key column is `hero_id`. Same with Hero and Team. A team can only have one hero as leader so we need to select leader when creating a team. Relationship name is `leader` and foreign key column is `leader_id`. Necessary codes are already added in previous step while updating the generated resource files. In `SkillResource.php` Forms\Components\Select::make('hero_id')->relationship('hero', 'name'), In `TeamResource.php` Forms\Components\Select::make('leader_id')->relationship('leader', 'name'), To show relationship object's attribute in table, you can just use relationship method name with `.` dot notation. Check the leader name column in `TeamResource.php`. It is not very convenient for user to create hero and then create many skills for the hero one by one at different page. Creating all the skills of the hero while creating the hero provides a better user experience. We can use Filament relation manger feature for that. We will need skill relation manager for the hero resource. **Relation manager** `php artisan make:filament-relation-manager HeroResource skills name` class SkillsRelationManager extends RelationManager { protected static string $relationship = 'skills'; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\TextInput::make('power')->numeric(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name'), Tables\Columns\TextColumn::make('power'), ]) ->filters([ // ]) ->headerActions([ Tables\Actions\CreateAction::make(), ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->bulkActions([ Tables\Actions\DeleteBulkAction::make(), ]); } } After generating and updating the relation manager, we need to update `getRelations()` method of hero resource as well. public static function getRelations(): array { return [ RelationManagers\SkillsRelationManager::class, ]; } A skills table and new skill button will appear at hero create/edit form.  **Many to many relationship** Team and hero have many to many relationship. We can either - add hero to one or more teams at hero create/edit form or - assign heroes at team create/edit form I like the later because most of the time we will be creating hero before creating team. We just update `TeamResource.php` public static function form(Form $form): Form { return $form ->schema([ //... Forms\Components\MultiSelect::make('heroes') ->relationship('heroes', 'name') ]); }New heroes multi-select field will appear at Team create/edit form  ## View Resource By default Filament doesn't generate view page since we will see all data at edit form anyway. If you want separate view page, you have to mention it during resource generation command. Or run generate view page command for existing resource. I will create own view page for hero resource. **Generate view page and update** `php artisan make:filament-page ViewHero --resource=HeroResource --type=ViewRecord` You will get a new `ViewHero.php` file under `HeroResource\pages\` directory. You need to add the view page to `getPages()` method in `HeroResource.php` public static function getPages(): array { return [ 'index' => Pages\ListHeroes::route('/'), 'create' => Pages\CreateHero::route('/create'), 'view' => Pages\ViewHero::route('/{record}'), 'edit' => Pages\EditHero::route('/{record}/edit'), ]; } Now when you click on a hero record, it will not bring you to edit form. You will still see all attributes in read-only state in form layout. To design your own custom view page, define `$view` property in `ViewHero.php` and create your own custom blade for the view. class ViewHero extends ViewRecord { protected static string $resource = HeroResource::class; protected static string $view = 'filament.resources.heroes.pages.view'; } You can use `$record` variable to get the resource object data in the blade file.{{ $record->name }}
{{ $record->history }}