
Tutorial Laravel Filament untuk Pemula
Panduan step-by-step belajar Laravel Filament dari dasar, membuat resource, widget, dashboard, dan fitur-fitur lengkap untuk pemula.
Tutorial Laravel Filament untuk Pemula
Apa itu Filament?
Filament adalah admin panel yang dibangun khusus untuk Laravel menggunakan TALL stack (Tailwind CSS, Alpine.js, Laravel, dan Livewire). Filament memungkinkan developer membuat interface admin yang modern, responsif, dan powerful dengan sangat cepat.
Mengapa Memilih Filament?
- Mudah dipelajari: Dokumentasi lengkap dan community support yang baik
- Produktif: Bisa membuat admin panel dalam hitungan menit
- Fleksibel: Sangat customizable sesuai kebutuhan
- Modern: Interface yang clean dan responsive
- Laravel Native: Terintegrasi sempurna dengan ecosystem Laravel

Persiapan Awal
1. Membuat Project Laravel Baru
Buat project Laravel 11 baru:
composer create-project --prefer-dist laravel/laravel filament-tutorial cd filament-tutorial
2. Setup Database
Edit file .env
:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=filament_tutorial DB_USERNAME=root DB_PASSWORD=
Buat database filament_tutorial
di MySQL/phpMyAdmin.
3. Install Filament
Install Filament via Composer:
composer require filament/filament:"^3.3" -W
Install Filament panels:
php artisan filament:install --panels
4. Buat User Admin
Buat user admin untuk akses panel:
php artisan make:filament-user
Input:
- Name: Admin
- Email: admin@example.com
- Password: password
Membuat Model dan Migration
1. Model Product
Kita akan membuat sistem manajemen produk sederhana. Buat model Product:
php artisan make:model Product -m
Edit migration create_products_table.php
:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->text('description')->nullable(); $table->decimal('price', 10, 2); $table->integer('stock')->default(0); $table->string('category'); $table->string('brand')->nullable(); $table->boolean('is_active')->default(true); $table->boolean('is_featured')->default(false); $table->json('images')->nullable(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('products'); } };
Edit model Product.php
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; class Product extends Model { protected $fillable = [ 'name', 'slug', 'description', 'price', 'stock', 'category', 'brand', 'is_active', 'is_featured', 'images' ]; protected $casts = [ 'price' => 'decimal:2', 'is_active' => 'boolean', 'is_featured' => 'boolean', 'images' => 'array' ]; protected static function boot() { parent::boot(); static::creating(function ($product) { if (!$product->slug) { $product->slug = Str::slug($product->name); } }); } public function getFormattedPriceAttribute(): string { return 'Rp ' . number_format($this->price, 0, ',', '.'); } }
2. Model Category
Buat model Category untuk relasi:
php artisan make:model Category -m
Edit migration create_categories_table.php
:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->text('description')->nullable(); $table->string('color')->default('#6366f1'); $table->boolean('is_active')->default(true); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('categories'); } };
Update migration Product untuk menambahkan foreign key:
php artisan make:migration add_category_id_to_products_table
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::table('products', function (Blueprint $table) { $table->foreignId('category_id')->nullable()->after('category')->constrained()->nullOnDelete(); }); } public function down(): void { Schema::table('products', function (Blueprint $table) { $table->dropForeign(['category_id']); $table->dropColumn('category_id'); }); } };
Edit model Category.php
:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Str; class Category extends Model { protected $fillable = [ 'name', 'slug', 'description', 'color', 'is_active' ]; protected $casts = [ 'is_active' => 'boolean' ]; protected static function boot() { parent::boot(); static::creating(function ($category) { if (!$category->slug) { $category->slug = Str::slug($category->name); } }); } public function products(): HasMany { return $this->hasMany(Product::class); } }
Update model Product untuk relasi:
// Tambahkan di model Product.php use Illuminate\Database\Eloquent\Relations\BelongsTo; public function category(): BelongsTo { return $this->belongsTo(Category::class); }
3. Jalankan Migration
php artisan migrate
Resource dengan --generate
1. Generate Category Resource
Filament menyediakan flag --generate
yang secara otomatis membuat form dan table berdasarkan struktur database:
php artisan make:filament-resource Category --generate
Perintah ini akan:
- Menganalisis struktur table categories
- Membuat form fields otomatis
- Membuat table columns otomatis
- Setup basic CRUD operations
2. Generate Product Resource
php artisan make:filament-resource Product --generate --view
Flag --view
akan menambahkan halaman view detail produk.
Mari kita lihat hasil generate dan kemudian modifikasi sesuai kebutuhan.
Modifikasi Category Resource
Edit file app/Filament/Resources/CategoryResource.php
:
<?php namespace App\Filament\Resources; use App\Filament\Resources\CategoryResource\Pages; use App\Models\Category; use Filament\Forms; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; class CategoryResource extends Resource { protected static ?string $model = Category::class; protected static ?string $navigationIcon = 'heroicon-o-tag'; protected static ?string $navigationLabel = 'Categories'; protected static ?string $modelLabel = 'Category'; protected static ?string $pluralModelLabel = 'Categories'; protected static ?int $navigationSort = 1; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\Section::make('Category Information') ->description('Enter the category details below') ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255) ->live(onBlur: true) ->afterStateUpdated(fn (string $operation, $state, Forms\Set $set) => $operation === 'create' ? $set('slug', \Illuminate\Support\Str::slug($state)) : null ) ->helperText('The name of the category'), Forms\Components\TextInput::make('slug') ->disabled() ->dehydrated() ->required() ->maxLength(255) ->unique(Category::class, 'slug', ignoreRecord: true) ->helperText('Auto-generated from name'), Forms\Components\ColorPicker::make('color') ->default('#6366f1') ->helperText('Choose a color for this category'), Forms\Components\Toggle::make('is_active') ->default(true) ->helperText('Enable or disable this category'), ]) ->columns(2), Forms\Components\Section::make('Additional Information') ->schema([ Forms\Components\Textarea::make('description') ->maxLength(1000) ->rows(3) ->helperText('Optional description for this category'), ]) ->collapsed() ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name') ->searchable() ->sortable() ->weight('bold'), Tables\Columns\TextColumn::make('slug') ->searchable() ->sortable() ->copyable() ->copyMessage('Slug copied!') ->badge() ->color('gray'), Tables\Columns\ColorColumn::make('color') ->label('Color'), Tables\Columns\TextColumn::make('products_count') ->counts('products') ->label('Products') ->badge() ->color('success'), Tables\Columns\IconColumn::make('is_active') ->boolean() ->label('Active') ->sortable(), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), Tables\Columns\TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\TernaryFilter::make('is_active') ->label('Active Status') ->boolean() ->trueLabel('Only Active Categories') ->falseLabel('Only Inactive Categories') ->native(false), Tables\Filters\Filter::make('has_products') ->query(fn ($query) => $query->has('products')) ->label('Has Products'), ]) ->actions([ Tables\Actions\ActionGroup::make([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), Tables\Actions\DeleteAction::make(), ]) ->label('Actions') ->icon('heroicon-m-ellipsis-vertical') ->size('sm') ->color('gray') ->button() ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), Tables\Actions\BulkAction::make('activate') ->label('Activate Selected') ->icon('heroicon-o-check-circle') ->color('success') ->action(fn ($records) => $records->each->update(['is_active' => true])) ->requiresConfirmation(), Tables\Actions\BulkAction::make('deactivate') ->label('Deactivate Selected') ->icon('heroicon-o-x-circle') ->color('danger') ->action(fn ($records) => $records->each->update(['is_active' => false])) ->requiresConfirmation(), ]), ]) ->emptyStateActions([ Tables\Actions\CreateAction::make(), ]) ->defaultSort('name', 'asc'); } public static function getPages(): array { return [ 'index' => Pages\ListCategories::route('/'), 'create' => Pages\CreateCategory::route('/create'), 'edit' => Pages\EditCategory::route('/{record}/edit'), ]; } public static function getNavigationBadge(): ?string { return static::getModel()::count(); } }
Modifikasi Product Resource
Edit file app/Filament/Resources/ProductResource.php
:
<?php namespace App\Filament\Resources; use App\Filament\Resources\ProductResource\Pages; use App\Models\Product; use App\Models\Category; use Filament\Forms; use Filament\Forms\Form; use Filament\Resources\Resource; use Filament\Tables; use Filament\Tables\Table; use Filament\Support\Enums\FontWeight; use Illuminate\Database\Eloquent\Builder; class ProductResource extends Resource { protected static ?string $model = Product::class; protected static ?string $navigationIcon = 'heroicon-o-cube'; protected static ?string $navigationLabel = 'Products'; protected static ?int $navigationSort = 2; protected static ?string $recordTitleAttribute = 'name'; public static function form(Form $form): Form { return $form ->schema([ Forms\Components\Group::make() ->schema([ Forms\Components\Section::make('Product Information') ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255) ->live(onBlur: true) ->afterStateUpdated(fn (string $operation, $state, Forms\Set $set) => $operation === 'create' ? $set('slug', \Illuminate\Support\Str::slug($state)) : null ), Forms\Components\TextInput::make('slug') ->disabled() ->dehydrated() ->required() ->maxLength(255) ->unique(Product::class, 'slug', ignoreRecord: true), Forms\Components\Textarea::make('description') ->maxLength(1000) ->rows(4) ->columnSpanFull(), ]) ->columns(2), Forms\Components\Section::make('Pricing & Inventory') ->schema([ Forms\Components\TextInput::make('price') ->required() ->numeric() ->prefix('Rp') ->minValue(0) ->step(0.01), Forms\Components\TextInput::make('stock') ->required() ->numeric() ->default(0) ->minValue(0) ->step(1), ]) ->columns(2), ]) ->columnSpan(['lg' => 2]), Forms\Components\Group::make() ->schema([ Forms\Components\Section::make('Associations') ->schema([ Forms\Components\Select::make('category_id') ->relationship('category', 'name') ->required() ->searchable() ->preload() ->createOptionForm([ Forms\Components\TextInput::make('name') ->required(), Forms\Components\ColorPicker::make('color') ->default('#6366f1'), Forms\Components\Toggle::make('is_active') ->default(true), ]), Forms\Components\TextInput::make('brand') ->maxLength(255), ]), Forms\Components\Section::make('Status') ->schema([ Forms\Components\Toggle::make('is_active') ->default(true) ->helperText('Enable/disable this product'), Forms\Components\Toggle::make('is_featured') ->helperText('Feature this product on homepage'), ]), ]) ->columnSpan(['lg' => 1]), ]) ->columns(3); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name') ->searchable() ->sortable() ->weight(FontWeight::Bold) ->description(fn (Product $record): string => $record->brand ?? 'No brand'), Tables\Columns\TextColumn::make('category.name') ->badge() ->searchable() ->sortable() ->color(fn ($record) => $record->category?->color ?? 'gray'), Tables\Columns\TextColumn::make('price') ->money('IDR') ->sortable() ->color('success') ->weight(FontWeight::Bold), Tables\Columns\TextColumn::make('stock') ->badge() ->sortable() ->color(fn (int $state): string => match (true) { $state === 0 => 'danger', $state < 10 => 'warning', default => 'success', }), Tables\Columns\IconColumn::make('is_featured') ->boolean() ->sortable() ->toggleable() ->label('Featured'), Tables\Columns\IconColumn::make('is_active') ->boolean() ->sortable() ->toggleable() ->label('Active'), Tables\Columns\TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\SelectFilter::make('category') ->relationship('category', 'name') ->searchable() ->preload() ->multiple(), Tables\Filters\Filter::make('price') ->form([ Forms\Components\TextInput::make('price_from') ->numeric() ->prefix('Rp'), Forms\Components\TextInput::make('price_to') ->numeric() ->prefix('Rp'), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['price_from'], fn (Builder $query, $price): Builder => $query->where('price', '>=', $price), ) ->when( $data['price_to'], fn (Builder $query, $price): Builder => $query->where('price', '<=', $price), ); }), Tables\Filters\TernaryFilter::make('is_featured') ->label('Featured') ->boolean() ->trueLabel('Only Featured') ->falseLabel('Not Featured') ->native(false), Tables\Filters\TernaryFilter::make('is_active') ->label('Active') ->boolean() ->trueLabel('Only Active') ->falseLabel('Only Inactive') ->native(false), Tables\Filters\Filter::make('low_stock') ->query(fn (Builder $query): Builder => $query->where('stock', '<', 10)) ->label('Low Stock'), ]) ->actions([ Tables\Actions\ActionGroup::make([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), Tables\Actions\Action::make('duplicate') ->icon('heroicon-o-document-duplicate') ->color('warning') ->action(function (Product $record) { $newProduct = $record->replicate(); $newProduct->name = $record->name . ' (Copy)'; $newProduct->slug = null; $newProduct->save(); }) ->successNotificationTitle('Product duplicated successfully'), Tables\Actions\DeleteAction::make(), ]) ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), Tables\Actions\BulkAction::make('feature') ->label('Mark as Featured') ->icon('heroicon-o-star') ->color('warning') ->action(fn ($records) => $records->each->update(['is_featured' => true])), Tables\Actions\BulkAction::make('unfeature') ->label('Remove from Featured') ->icon('heroicon-o-star') ->color('gray') ->action(fn ($records) => $records->each->update(['is_featured' => false])), ]), ]) ->emptyStateActions([ Tables\Actions\CreateAction::make(), ]) ->defaultSort('created_at', 'desc'); } public static function getPages(): array { return [ 'index' => Pages\ListProducts::route('/'), 'create' => Pages\CreateProduct::route('/create'), 'view' => Pages\ViewProduct::route('/{record}'), 'edit' => Pages\EditProduct::route('/{record}/edit'), ]; } public static function getNavigationBadge(): ?string { return static::getModel()::count(); } public static function getGloballySearchableAttributes(): array { return ['name', 'brand', 'category.name']; } }
Membuat Widget untuk Dashboard
1. Stats Overview Widget
php artisan make:filament-widget StatsOverview --stats
Edit app/Filament/Widgets/StatsOverview.php
:
<?php namespace App\Filament\Widgets; use App\Models\Product; use App\Models\Category; use Filament\Widgets\StatsOverviewWidget as BaseWidget; use Filament\Widgets\StatsOverviewWidget\Stat; class StatsOverview extends BaseWidget { protected static ?int $sort = 0; protected function getStats(): array { $totalProducts = Product::count(); $activeProducts = Product::where('is_active', true)->count(); $totalCategories = Category::count(); $lowStockProducts = Product::where('stock', '<', 10)->count(); $featuredProducts = Product::where('is_featured', true)->count(); $totalValue = Product::sum('price'); return [ Stat::make('Total Products', $totalProducts) ->description('All products in database') ->descriptionIcon('heroicon-m-cube') ->color('primary') ->extraAttributes([ 'class' => 'cursor-pointer', ]), Stat::make('Active Products', $activeProducts) ->description($totalProducts > 0 ? round(($activeProducts / $totalProducts) * 100, 1) . '% of total' : '0%') ->descriptionIcon('heroicon-m-eye') ->color('success'), Stat::make('Categories', $totalCategories) ->description('Product categories') ->descriptionIcon('heroicon-m-tag') ->color('warning'), Stat::make('Low Stock Items', $lowStockProducts) ->description('Products with stock < 10') ->descriptionIcon($lowStockProducts > 0 ? 'heroicon-m-exclamation-triangle' : 'heroicon-m-check-circle') ->color($lowStockProducts > 0 ? 'danger' : 'success'), Stat::make('Featured Products', $featuredProducts) ->description('Products marked as featured') ->descriptionIcon('heroicon-m-star') ->color('info'), Stat::make('Total Inventory Value', 'Rp ' . number_format($totalValue, 0, ',', '.')) ->description('Sum of all product prices') ->descriptionIcon('heroicon-m-currency-dollar') ->color('success'), ]; } }
2. Latest Products Widget
php artisan make:filament-widget LatestProducts
Edit app/Filament/Widgets/LatestProducts.php
:
<?php namespace App\Filament\Widgets; use App\Models\Product; use Filament\Tables; use Filament\Tables\Table; use Filament\Widgets\TableWidget as BaseWidget; class LatestProducts extends BaseWidget { protected static ?int $sort = 2; protected int | string | array $columnSpan = 'full'; public function table(Table $table): Table { return $table ->query(Product::query()->latest()->limit(5)) ->heading('Latest Products') ->columns([ Tables\Columns\TextColumn::make('name') ->weight('bold') ->searchable(), Tables\Columns\TextColumn::make('category.name') ->badge() ->color(fn ($record) => $record->category?->color ?? 'gray'), Tables\Columns\TextColumn::make('price') ->money('IDR') ->color('success') ->weight('bold'), Tables\Columns\TextColumn::make('stock') ->badge() ->color(fn (int $state): string => match (true) { $state === 0 => 'danger', $state < 10 => 'warning', default => 'success', }), Tables\Columns\IconColumn::make('is_active') ->boolean(), Tables\Columns\TextColumn::make('created_at') ->since() ->dateTimeTooltip(), ]) ->actions([ Tables\Actions\Action::make('view') ->url(fn (Product $record): string => ProductResource::getUrl('edit', ['record' => $record])) ->icon('heroicon-o-eye'), ]); } }
3. Product Chart Widget
php artisan make:filament-widget ProductChart --chart
Edit app/Filament/Widgets/ProductChart.php
:
<?php namespace App\Filament\Widgets; use App\Models\Category; use Filament\Widgets\ChartWidget; class ProductChart extends ChartWidget { protected static ?string $heading = 'Products by Category'; protected static ?int $sort = 1; protected function getData(): array { $categories = Category::withCount('products')->get(); return [ 'datasets' => [ [ 'label' => 'Products per Category', 'data' => $categories->pluck('products_count')->toArray(), 'backgroundColor' => $categories->pluck('color')->toArray(), 'borderColor' => $categories->pluck('color')->toArray(), ], ], 'labels' => $categories->pluck('name')->toArray(), ]; } protected function getType(): string { return 'doughnut'; } protected function getOptions(): array { return [ 'plugins' => [ 'legend' => [ 'display' => true, ], ], ]; } }
Customisasi Dashboard
1. Custom Dashboard Page
php artisan make:filament-page Dashboard --type=custom
Edit app/Filament/Pages/Dashboard.php
:
<?php namespace App\Filament\Pages; use Filament\Pages\Dashboard as BaseDashboard; class Dashboard extends BaseDashboard { protected static ?string $navigationIcon = 'heroicon-o-home'; protected static string $routePath = '/'; public function getColumns(): int | string | array { return [ 'sm' => 1, 'md' => 2, 'lg' => 3, 'xl' => 4, ]; } public function getWidgets(): array { return [ \App\Filament\Widgets\StatsOverview::class, \App\Filament\Widgets\ProductChart::class, \App\Filament\Widgets\LatestProducts::class, ]; } }
2. Menambahkan Navigation Groups
Edit file app/Providers/Filament/AdminPanelProvider.php
:
<?php namespace App\Providers\Filament; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Pages; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; use Filament\Widgets; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\Session\Middleware\StartSession; use Illuminate\View\Middleware\ShareErrorsFromSession; class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { return $panel ->default() ->id('admin') ->path('admin') ->login() ->colors([ 'primary' => Color::Blue, ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->pages([ Pages\Dashboard::class, ]) ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') ->widgets([ // Widgets\AccountWidget::class, // Widgets\FilamentInfoWidget::class, ]) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, AuthenticateSession::class, ShareErrorsFromSession::class, VerifyCsrfToken::class, SubstituteBindings::class, DisableBladeIconComponents::class, DispatchServingFilamentEvent::class, ]) ->authMiddleware([ Authenticate::class, ]) ->navigationGroups([ 'Product Management', 'Analytics', 'System', ]) ->sidebarCollapsibleOnDesktop() ->brandName('My Admin Panel') ->favicon('/favicon.ico'); } }
3. Update Resource dengan Navigation Groups
Update CategoryResource.php:
// Tambahkan di CategoryResource protected static ?string $navigationGroup = 'Product Management';
Update ProductResource.php:
// Tambahkan di ProductResource protected static ?string $navigationGroup = 'Product Management';
Seeder untuk Data Sample
1. Category Seeder
php artisan make:seeder CategorySeeder
Edit database/seeders/CategorySeeder.php
:
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Category; class CategorySeeder extends Seeder { public function run(): void { $categories = [ [ 'name' => 'Electronics', 'description' => 'Electronic devices and gadgets', 'color' => '#3B82F6', 'is_active' => true, ], [ 'name' => 'Clothing', 'description' => 'Fashion and apparel', 'color' => '#EF4444', 'is_active' => true, ], [ 'name' => 'Books', 'description' => 'Books and educational materials', 'color' => '#10B981', 'is_active' => true, ], [ 'name' => 'Sports', 'description' => 'Sports equipment and accessories', 'color' => '#F59E0B', 'is_active' => true, ], [ 'name' => 'Home & Garden', 'description' => 'Home improvement and garden supplies', 'color' => '#8B5CF6', 'is_active' => true, ], ]; foreach ($categories as $category) { Category::create($category); } } }
2. Product Seeder
php artisan make:seeder ProductSeeder
Edit database/seeders/ProductSeeder.php
:
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\Product; use App\Models\Category; class ProductSeeder extends Seeder { public function run(): void { $electronics = Category::where('name', 'Electronics')->first(); $clothing = Category::where('name', 'Clothing')->first(); $books = Category::where('name', 'Books')->first(); $sports = Category::where('name', 'Sports')->first(); $home = Category::where('name', 'Home & Garden')->first(); $products = [ // Electronics [ 'name' => 'iPhone 15 Pro', 'description' => 'Latest iPhone with advanced features', 'price' => 15000000, 'stock' => 25, 'category_id' => $electronics->id, 'category' => 'Electronics', 'brand' => 'Apple', 'is_active' => true, 'is_featured' => true, ], [ 'name' => 'Samsung Galaxy S24', 'description' => 'Premium Android smartphone', 'price' => 12000000, 'stock' => 30, 'category_id' => $electronics->id, 'category' => 'Electronics', 'brand' => 'Samsung', 'is_active' => true, 'is_featured' => true, ], [ 'name' => 'MacBook Air M3', 'description' => 'Lightweight laptop with M3 chip', 'price' => 20000000, 'stock' => 15, 'category_id' => $electronics->id, 'category' => 'Electronics', 'brand' => 'Apple', 'is_active' => true, 'is_featured' => false, ], [ 'name' => 'AirPods Pro', 'description' => 'Wireless earbuds with noise cancellation', 'price' => 3500000, 'stock' => 5, // Low stock 'category_id' => $electronics->id, 'category' => 'Electronics', 'brand' => 'Apple', 'is_active' => true, 'is_featured' => true, ], // Clothing [ 'name' => 'Nike Air Force 1', 'description' => 'Classic white sneakers', 'price' => 1500000, 'stock' => 50, 'category_id' => $clothing->id, 'category' => 'Clothing', 'brand' => 'Nike', 'is_active' => true, 'is_featured' => false, ], [ 'name' => 'Uniqlo Basic T-Shirt', 'description' => 'Comfortable cotton t-shirt', 'price' => 150000, 'stock' => 100, 'category_id' => $clothing->id, 'category' => 'Clothing', 'brand' => 'Uniqlo', 'is_active' => true, 'is_featured' => false, ], [ 'name' => 'Levi\'s 501 Jeans', 'description' => 'Classic denim jeans', 'price' => 800000, 'stock' => 35, 'category_id' => $clothing->id, 'category' => 'Clothing', 'brand' => 'Levi\'s', 'is_active' => true, 'is_featured' => true, ], // Books [ 'name' => 'Laravel: Up & Running', 'description' => 'Comprehensive guide to Laravel development', 'price' => 500000, 'stock' => 20, 'category_id' => $books->id, 'category' => 'Books', 'brand' => 'O\'Reilly', 'is_active' => true, 'is_featured' => true, ], [ 'name' => 'Clean Code', 'description' => 'A handbook of agile software craftsmanship', 'price' => 600000, 'stock' => 15, 'category_id' => $books->id, 'category' => 'Books', 'brand' => 'Prentice Hall', 'is_active' => true, 'is_featured' => false, ], // Sports [ 'name' => 'Wilson Tennis Racket', 'description' => 'Professional tennis racket', 'price' => 2500000, 'stock' => 8, // Low stock 'category_id' => $sports->id, 'category' => 'Sports', 'brand' => 'Wilson', 'is_active' => true, 'is_featured' => false, ], [ 'name' => 'Yoga Mat Premium', 'description' => 'High-quality yoga mat for exercise', 'price' => 350000, 'stock' => 25, 'category_id' => $sports->id, 'category' => 'Sports', 'brand' => 'Manduka', 'is_active' => true, 'is_featured' => false, ], // Home & Garden [ 'name' => 'IKEA Office Chair', 'description' => 'Ergonomic office chair', 'price' => 1200000, 'stock' => 12, 'category_id' => $home->id, 'category' => 'Home & Garden', 'brand' => 'IKEA', 'is_active' => true, 'is_featured' => false, ], [ 'name' => 'LED Desk Lamp', 'description' => 'Adjustable LED desk lamp', 'price' => 250000, 'stock' => 0, // Out of stock 'category_id' => $home->id, 'category' => 'Home & Garden', 'brand' => 'Philips', 'is_active' => false, // Inactive due to no stock 'is_featured' => false, ], ]; foreach ($products as $product) { Product::create($product); } } }
3. Update DatabaseSeeder
Edit database/seeders/DatabaseSeeder.php
:
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { public function run(): void { $this->call([ CategorySeeder::class, ProductSeeder::class, ]); } }
Jalankan seeder:
php artisan db:seed
Fitur Advanced
1. Global Search
Tambahkan global search di ProductResource:
// Sudah ada di ProductResource di atas public static function getGloballySearchableAttributes(): array { return ['name', 'brand', 'category.name']; } public static function getGlobalSearchResultDetails(Model $record): array { return [ 'Category' => $record->category?->name, 'Brand' => $record->brand, 'Price' => $record->formatted_price, ]; } public static function getGlobalSearchResultActions(Model $record): array { return [ Action::make('edit') ->iconButton() ->icon('heroicon-o-pencil-square') ->url(static::getUrl('edit', ['record' => $record])), ]; }
2. Custom Actions
Tambahkan custom action untuk duplicate product di ProductResource:
// Sudah ada di table actions di atas Tables\Actions\Action::make('duplicate') ->icon('heroicon-o-document-duplicate') ->color('warning') ->action(function (Product $record) { $newProduct = $record->replicate(); $newProduct->name = $record->name . ' (Copy)'; $newProduct->slug = null; $newProduct->save(); }) ->successNotificationTitle('Product duplicated successfully'),
3. Custom Bulk Actions
Tambahkan bulk actions untuk mengubah status:
// Sudah ada di bulkActions di atas Tables\Actions\BulkAction::make('feature') ->label('Mark as Featured') ->icon('heroicon-o-star') ->color('warning') ->action(fn ($records) => $records->each->update(['is_featured' => true])), Tables\Actions\BulkAction::make('unfeature') ->label('Remove from Featured') ->icon('heroicon-o-star') ->color('gray') ->action(fn ($records) => $records->each->update(['is_featured' => false])),
4. Advanced Filters
Tambahkan filter berdasarkan range harga:
// Sudah ada di filters di atas Tables\Filters\Filter::make('price') ->form([ Forms\Components\TextInput::make('price_from') ->numeric() ->prefix('Rp'), Forms\Components\TextInput::make('price_to') ->numeric() ->prefix('Rp'), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['price_from'], fn (Builder $query, $price): Builder => $query->where('price', '>=', $price), ) ->when( $data['price_to'], fn (Builder $query, $price): Builder => $query->where('price', '<=', $price), ); }),
Customisasi Appearance
1. Custom Theme Colors
Edit app/Providers/Filament/AdminPanelProvider.php
:
->colors([ 'primary' => Color::Blue, 'gray' => Color::Slate, 'success' => Color::Green, 'warning' => Color::Amber, 'danger' => Color::Red, 'info' => Color::Sky, ])
2. Custom Brand Logo
->brandLogo(asset('images/logo.png')) ->brandLogoHeight('2rem') ->brandName('My Store Admin')
3. Dark Mode
->darkMode(false) // Disable dark mode // atau ->darkMode(true) // Force dark mode
Menjalankan Aplikasi
1. Build Assets
npm install npm run build
2. Start Server
php artisan serve
3. Akses Admin Panel
Buka browser dan akses http://localhost:8000/admin
Login dengan:
- Email: admin@example.com
- Password: password
Fitur-Fitur Penting yang Dipelajari
1. Resource Management
- ✅ Generate Resource: Menggunakan
--generate
flag - ✅ CRUD Operations: Create, Read, Update, Delete
- ✅ Form Components: TextInput, Select, Toggle, dll
- ✅ Table Columns: TextColumn, BadgeColumn, IconColumn, dll
- ✅ Relationships: BelongsTo, HasMany
2. Search & Filter
- ✅ Searchable: Kolom yang bisa dicari
- ✅ Sortable: Kolom yang bisa diurutkan
- ✅ Filters: SelectFilter, TernaryFilter, Custom Filter
- ✅ Global Search: Search across resources
3. Actions & Bulk Actions
- ✅ Row Actions: View, Edit, Delete, Custom
- ✅ Bulk Actions: Multiple record operations
- ✅ Custom Actions: Business logic specific actions
4. Dashboard & Widgets
- ✅ Stats Widget: Statistik overview
- ✅ Chart Widget: Visualisasi data
- ✅ Table Widget: Tabel data latest
- ✅ Custom Dashboard: Layout dan widget management
5. Advanced Features
- ✅ Validation: Form validation
- ✅ Notifications: Success/error messages
- ✅ Navigation: Groups, badges, icons
- ✅ Theming: Colors, branding
- ✅ Seeders: Sample data
Tips & Best Practices
1. Performance
// Gunakan eager loading public static function getEloquentQuery(): Builder { return parent::getEloquentQuery()->with(['category']); } // Pagination protected static ?string $recordsPerPageSelectOption = '25,50,100';
2. Security
// Batasi akses berdasarkan user public static function getEloquentQuery(): Builder { return parent::getEloquentQuery()->where('user_id', auth()->id()); }
3. UX/UI
// Gunakan helper text Forms\Components\TextInput::make('slug') ->helperText('Auto-generated from name'), // Confirmations untuk actions berbahaya ->requiresConfirmation() ->modalHeading('Delete product') ->modalDescription('Are you sure you want to delete this product?')
4. Data Integrity
// Soft deletes use Illuminate\Database\Eloquent\SoftDeletes; class Product extends Model { use SoftDeletes; } // Validasi unique Forms\Components\TextInput::make('slug') ->unique(Product::class, 'slug', ignoreRecord: true)
Troubleshooting Common Issues
1. Migration Issues
# Reset dan re-run migrations php artisan migrate:fresh --seed
2. Cache Issues
# Clear semua cache php artisan optimize:clear
3. Permission Issues
# Fix storage permissions php artisan storage:link chmod -R 775 storage bootstrap/cache
4. Asset Issues
# Rebuild assets npm run build
Next Steps untuk Pengembangan
1. Advanced Features
- File Uploads: Image galleries untuk products
- Multi-language: Internationalization
- Exports: PDF, Excel exports
- Imports: Bulk data import
- API: REST API dengan Laravel Sanctum
2. Third-party Integrations
- Payment Gateway: Stripe, PayPal
- Email: Mail notifications
- Storage: Amazon S3, Google Cloud
- Analytics: Google Analytics integration
3. Advanced Widgets
- Real-time Data: WebSockets dengan Pusher
- Advanced Charts: Chart.js, ApexCharts
- Maps: Google Maps integration
- Calendar: Event management
Kesimpulan
Tutorial ini telah mengcover semua aspek fundamental Filament dari level pemula hingga intermediate:
🎯 Yang Telah Dipelajari:
- Setup dan instalasi Filament
- Resource generation dan customization
- Form dan table management
- Search, filter, dan sorting
- Actions dan bulk actions
- Widget dan dashboard creation
- Navigation dan theming
- Data seeding dan best practices
🚀 Skills yang Didapat:
- Mampu membuat admin panel modern dengan cepat
- Memahami konsep Resource, Widget, dan Dashboard
- Menguasai customization form dan table
- Implementasi search, filter, dan actions
- Best practices untuk performance dan security
Filament adalah tool yang sangat powerful untuk membuat admin panel Laravel. Dengan foundation yang solid dari tutorial ini, kalian sudah bisa mengembangkan aplikasi admin yang complex dan professional.
Selamat mencoba! Jangan ragu untuk eksperimen dan mengembangkan fitur-fitur tambahan sesuai kebutuhan project kalian.
Resources untuk lanjutan: