September 27, 2026

Laravel Filament.

Membangun admin panel dengan Filament 5: panel, resource CRUD, form, tabel, action, widget dashboard, otorisasi, dan kapan lebih baik membuat admin sendiri.

Filament adalah kumpulan komponen full-stack untuk Laravel yang dibangun di atas Livewire, Alpine.js, dan Tailwind CSS. Paling terkenal sebagai admin panel builder: dengan beberapa perintah Artisan, kamu mendapat halaman login, dashboard, dan CRUD lengkap (tabel dengan search, filter, sort, pagination, plus form dengan validasi) β€” semuanya ditulis dalam PHP, tanpa menulis JavaScript.

Versi: Filament 5 (Januari 2026) secara fitur identik dengan Filament 4; perbedaannya adalah dukungan Livewire 4. Kedua versi masih dirawat. Sintaks di catatan ini berlaku untuk v4 dan v5. Tutorial lama (v2/v3) memakai struktur yang berbeda β€” perhatikan versinya saat mencari contoh.


1. Instalasi

Syarat: PHP 8.2+, Laravel 11.28+ (Laravel 13 butuh PHP 8.3+), Tailwind CSS 4.1+ jika kamu membuat tema kustom.

composer require filament/filament:"^5.0"
php artisan filament:install --panels     # membuat app/Providers/Filament/AdminPanelProvider.php
php artisan make:filament-user            # buat akun admin

Buka http://localhost:8000/admin.

Panel dikonfigurasi di AdminPanelProvider:

use Filament\Panel;
use Filament\Support\Colors\Color;

public function panel(Panel $panel): Panel
{
    return $panel
        ->default()
        ->id('admin')
        ->path('admin')
        ->login()
        ->colors(['primary' => Color::Amber])
        ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
        ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
        ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets');
}

Satu aplikasi bisa punya beberapa panel (misal /admin untuk staf, /app untuk pelanggan): php artisan make:filament-panel app.


2. Resource: CRUD dari Model

php artisan make:filament-resource Product --generate
OpsiFungsi
--generateMenebak field form & kolom tabel dari struktur tabel database
--viewTambah halaman View (read-only)
--soft-deletesDukungan restore / force delete
--simpleCRUD dalam modal di satu halaman β€œManage” (cocok untuk data kecil)

Struktur yang dihasilkan (v4/v5):

app/Filament/Resources/Products/
β”œβ”€β”€ ProductResource.php
β”œβ”€β”€ Pages/
β”‚   β”œβ”€β”€ CreateProduct.php
β”‚   β”œβ”€β”€ EditProduct.php
β”‚   └── ListProducts.php
β”œβ”€β”€ Schemas/
β”‚   └── ProductForm.php
└── Tables/
    └── ProductsTable.php

ProductResource hanya mendelegasikan:

use Filament\Schemas\Schema;
use Filament\Tables\Table;

public static function form(Schema $schema): Schema
{
    return ProductForm::configure($schema);
}

public static function table(Table $table): Table
{
    return ProductsTable::configure($table);
}

3. Form

<?php

namespace App\Filament\Resources\Products\Schemas;

use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Illuminate\Support\Str;

class ProductForm
{
    public static function configure(Schema $schema): Schema
    {
        return $schema->components([
            Section::make('Informasi Produk')
                ->columns(2)
                ->schema([
                    TextInput::make('name')
                        ->required()
                        ->maxLength(255)
                        ->live(onBlur: true)
                        ->afterStateUpdated(fn ($state, $set) => $set('slug', Str::slug($state))),
                    TextInput::make('slug')
                        ->required()
                        ->unique(ignoreRecord: true),
                    Select::make('category_id')
                        ->relationship('category', 'name')
                        ->searchable()
                        ->preload()
                        ->required(),
                    TextInput::make('price')
                        ->numeric()
                        ->prefix('Rp')
                        ->minValue(0)
                        ->required(),
                    Toggle::make('is_active')->default(true),
                ]),
            RichEditor::make('description')->columnSpanFull(),
            FileUpload::make('image')->image()->directory('products'),
        ]);
    }
}

Validasi ditulis sebagai method di field (required(), unique(), maxLength()), dan Filament menyimpan data ke model secara otomatis β€” pastikan kolom-kolomnya fillable (lihat Eloquent ORM ).


4. Tabel

<?php

namespace App\Filament\Resources\Products\Tables;

use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;

class ProductsTable
{
    public static function configure(Table $table): Table
    {
        return $table
            ->columns([
                ImageColumn::make('image'),
                TextColumn::make('name')->searchable()->sortable(),
                TextColumn::make('category.name')->label('Kategori')->sortable(),
                TextColumn::make('price')->money('IDR')->sortable(),
                IconColumn::make('is_active')->boolean(),
                TextColumn::make('created_at')->since()->toggleable(isToggledHiddenByDefault: true),
            ])
            ->defaultSort('created_at', 'desc')
            ->filters([
                SelectFilter::make('category')->relationship('category', 'name'),
                TernaryFilter::make('is_active'),
            ])
            ->recordActions([
                EditAction::make(),
            ])
            ->toolbarActions([
                BulkActionGroup::make([
                    DeleteBulkAction::make(),
                ]),
            ]);
    }
}

Relasi di kolom (category.name) otomatis di-eager-load, jadi aman dari N+1 untuk kasus umum.


5. Action

Action adalah tombol yang menjalankan sesuatu, opsional dengan modal konfirmasi atau form. Sejak v4, semua action memakai namespace yang sama: Filament\Actions\....

use Filament\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;

Action::make('adjustStock')
    ->label('Atur Stok')
    ->icon('heroicon-o-archive-box')
    ->schema([
        TextInput::make('qty')->numeric()->required(),
    ])
    ->action(function (array $data, Product $record): void {
        $record->increment('stock', $data['qty']);

        Notification::make()->title('Stok diperbarui')->success()->send();
    })
    ->requiresConfirmation()
    ->visible(fn (Product $record) => $record->is_active);

Pasang di recordActions([...]) pada tabel, atau di header halaman lewat method getHeaderActions() pada page class.


6. Widget Dashboard

php artisan make:filament-widget StatsOverview --stats-overview
php artisan make:filament-widget OrdersChart --chart
use App\Models\Order;
use App\Models\Product;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class StatsOverview extends StatsOverviewWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Produk aktif', Product::where('is_active', true)->count()),
            Stat::make('Order hari ini', Order::whereDate('created_at', today())->count())
                ->description('Termasuk yang belum dibayar')
                ->color('success'),
            Stat::make('Omzet bulan ini', 'Rp ' . number_format(
                Order::whereMonth('created_at', now()->month)->sum('total'), 0, ',', '.'
            )),
        ];
    }
}

Widget yang ditaruh di app/Filament/Widgets otomatis tampil di dashboard.


7. Otorisasi & Akses Panel

  1. Siapa yang boleh masuk panel. Di environment production, Filament menolak semua user (403) sampai model User mengimplementasikan FilamentUser:
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;

class User extends Authenticatable implements FilamentUser
{
    public function canAccessPanel(Panel $panel): bool
    {
        return $this->is_admin && $this->hasVerifiedEmail();
    }
}
  1. Apa yang boleh dilakukan di resource. Filament mengikuti Laravel Policy (viewAny, create, update, delete, deleteAny, dst.):
php artisan make:policy ProductPolicy --model=Product

Untuk role & permission yang lebih kompleks, kombinasikan dengan spatie/laravel-permission atau plugin seperti Filament Shield.


8. Filament vs Admin Custom

PertimbanganFilamentAdmin custom (Blade / Inertia + React)
Kecepatan membuat CRUDSangat cepat (menit per resource)Lambat, semuanya ditulis manual
Konsistensi UIOtomatis konsistenTergantung disiplin tim
Desain unik / brand kuatTerbatas pada tema & komponen FilamentBebas penuh
Interaksi sangat kustom (drag-drop kompleks, editor visual)Bisa lewat custom page/Livewire, tapi melawan arusLebih natural
Stack frontendLivewire (PHP) β€” tidak memakai ReactSatu stack dengan frontend publik
Cocok untukBack-office, CMS, data master, laporan internalAdmin yang juga jadi produk utama untuk user

Contoh nyata: Watch Store memakai admin panel Blade buatan sendiri; untuk kebutuhan CRUD produk, order, dan laporan standar seperti itu, Filament bisa memangkas waktu pengerjaan secara signifikan. Sebaliknya, aplikasi yang β€œadmin”-nya adalah produk itu sendiri β€” misal dashboard pemilik kos di Kos Management App β€” lebih cocok dengan UI custom.


9. Pitfalls

  • 403 di produksi padahal di lokal bisa login β†’ belum mengimplementasikan FilamentUser::canAccessPanel().
  • Mengikuti tutorial v3 β†’ namespace berbeda (Filament\Tables\Actions\EditAction di v3 vs Filament\Actions\EditAction di v4/v5, Form $form vs Schema $schema). Gunakan upgrade script resmi saat migrasi.
  • Data tidak tersimpan β†’ kolom tidak ada di $fillable / #[Fillable].
  • Upload file tidak tampil β†’ belum php artisan storage:link atau APP_URL salah.
  • Tabel lambat pada data besar β†’ tambahkan index DB pada kolom yang searchable()/sortable(), dan hindari kolom hasil komputasi berat per baris.
  • Setelah upgrade, asset/tampilan rusak β†’ jalankan php artisan filament:upgrade (biasanya sudah ada di script post-autoload-dump composer).
  • Menjadikan Filament untuk halaman publik yang butuh desain khusus β†’ biasanya lebih repot daripada membuat halaman Laravel + React biasa.

Referensi: Filament Docs 5.x , Resources , Tables , Actions , Widgets , Introducing Filament v5 .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments