Using FilamentPHP Resources to Build Admin UIs Without Writing Blade Templates
Learn how FilamentPHP Resources let you build admin CRUD UIs in plain PHP, with a concrete Post example, benefits, and practical limitations.
03 May 2026, 00:59 UTC

The problem: repetitive admin CRUD code
When you need an admin panel for a Laravel application, the typical path is to create routes, controllers, Blade views, and form requests for each model. This boilerplate grows quickly, especially when you have dozens of resources that share similar create‑read‑update‑delete patterns.
Thesis: FilamentPHP’s Resource class lets you describe the entire CRUD UI in plain PHP, giving you a testable, version‑controlled admin interface while still allowing deep customization when needed.
How a Resource works
FilamentPHP provides a base Filament\Resources\Resource class. By extending it, you define three main sections:
form()– fields that appear on create/edit pages.table()– columns, filters, and bulk actions for the index view.actions()– custom buttons or dropdowns on table rows.
All definitions use a fluent API, so the code reads like a description of the UI rather than a series of imperative steps.
Worked example: a Post resource
Assume you already have a Laravel project with an App\Models\Post Eloquent model. The steps below show how to generate a fully functional admin UI for posts.
- Install Filament. Run this command in your project root (you need Composer access and write permission to the
vendordirectory):composer require filament/filament - Publish the admin assets and create the admin user.
The installer adds the service provider, publishes config, and sets up Livewire assets.php artisan filament:install php artisan make:filament-user - Generate the Resource. The
--generateflag creates a starter class with fields based on the model’s columns.
This createsphp artisan make:filament-resource Post --generateapp/Filament/Resources/PostResource.phpand a corresponding navigation item. - Inspect the generated class. Open the file; you’ll see something like:
class PostResource extends Resource { protected static ?string $model = App\Models\Post::class; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\TextInput::make('title') ->required() ->maxLength(255), Forms\Components\Textarea::make('body') ->columnSpanFull(), Forms\Components\DateTimePicker::make('published_at') ->nullable(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('title') ->sortable() ->searchable(), Tables\Columns\TextColumn::make('body') ->limit(50), Tables\Columns\DateTimeColumn::make('published_at') ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ // example filter Filters\Filter::make('published') ->boolean() ->label('Published') ->query(fn (Builder $query) => $query->whereNotNull('published_at')), ]) ->actions([ Tables\Actions\EditAction::make(), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } public static function getRelations(): array { return [ // RelationManagers can be added here ]; } public static function getPages(): array { return [ 'index' => Pages\ListPosts::route('/'), 'create' => Pages\CreatePost::route('/create'), 'edit' => Pages\EditPost::route('/edit/{record}'), ]; } } - Verify the UI. Start the development server (
php artisan serve) and navigate to/admin/posts. You should see a table with the columns defined above, a "Create Post" button, and inline editing capabilities powered by Livewire.
Trade‑offs and limitations
While the Resource class speeds up UI creation, there are practical considerations:
- Livewire memory usage. Each admin page creates a Livewire component that lives in the session. Under high concurrent traffic, this can increase RAM consumption. Monitor memory with tools like
php -r "echo memory_get_usage();"or Laravel Telescope, and consider pagination or lazy loading for large tables. - Mixing presentation and business logic. Because the Resource class is plain PHP, it’s tempting to put validation rules, authorization checks, or even service calls directly inside
form()ortable(). Over time this blurs the separation of concerns. A safer approach is to keep the Resource focused on UI description and delegate complex logic to form request classes, policy classes, or service containers. - Custom Blade overrides. If you need a completely custom layout for a specific page, you can publish the generated Blade views (
php artisan filament:publish) and edit them, but then you lose the automatic updates that come from the Resource class.
Actionable closing
Start by generating a Resource for each core model using the make:filament-resource --generate command. Keep the class thin: define fields, columns, and actions, but move any non‑UI logic to dedicated request objects, policies, or services. Test the Resource class in isolation with PHPUnit—you can instantiate it and call form() or table() without a running browser to ensure the UI description is correct. Finally, monitor Livewire session memory in staging and adjust pagination or component limits as your admin traffic grows.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.