Skip to content

Extensões e Customizações

Aprenda a estender o Filament Core Starter Kit para atender suas necessidades específicas.

Adicionar Novos Módulos

Criar Módulo Customizado

Exemplo: Sistema de Tarefas (Tasks)

1. Criar Model e Migration

bash
php artisan make:model Task -mfs
php
// database/migrations/xxxx_create_tasks_table.php
Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable();
    $table->foreignId('person_id')->constrained()->cascadeOnDelete();
    $table->foreignId('assigned_to')->nullable()->constrained('users');
    $table->enum('status', ['pending', 'in_progress', 'completed', 'cancelled']);
    $table->enum('priority', ['low', 'medium', 'high', 'urgent']);
    $table->timestamp('due_date')->nullable();
    $table->timestamps();
    $table->softDeletes();
});
php
// app/Models/Task.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Task extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'title',
        'description',
        'person_id',
        'assigned_to',
        'status',
        'priority',
        'due_date',
    ];

    protected $casts = [
        'due_date' => 'datetime',
    ];

    public function person(): BelongsTo
    {
        return $this->belongsTo(Person::class);
    }

    public function assignedUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'assigned_to');
    }
}

2. Criar Filament Resource

bash
php artisan make:filament-resource Task
php
// app/Filament/Resources/TaskResource.php
namespace App\Filament\Resources;

use Filament\Forms;
use Filament\Tables;
use Filament\Resources\Resource;
use App\Models\Task;

class TaskResource extends Resource
{
    protected static ?string $model = Task::class;
    protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
    protected static ?string $navigationGroup = 'Gestão';

    public static function form(Forms\Form $form): Forms\Form
    {
        return $form
            ->schema([
                Forms\Components\Section::make('Informações da Tarefa')
                    ->schema([
                        Forms\Components\TextInput::make('title')
                            ->label('Título')
                            ->required()
                            ->maxLength(255),

                        Forms\Components\Textarea::make('description')
                            ->label('Descrição')
                            ->rows(4)
                            ->columnSpanFull(),

                        Forms\Components\Select::make('person_id')
                            ->label('Cliente')
                            ->relationship('person', 'name')
                            ->searchable()
                            ->required(),

                        Forms\Components\Select::make('assigned_to')
                            ->label('Atribuído a')
                            ->relationship('assignedUser', 'name')
                            ->searchable(),

                        Forms\Components\Select::make('priority')
                            ->label('Prioridade')
                            ->options([
                                'low' => 'Baixa',
                                'medium' => 'Média',
                                'high' => 'Alta',
                                'urgent' => 'Urgente',
                            ])
                            ->required()
                            ->default('medium'),

                        Forms\Components\Select::make('status')
                            ->label('Status')
                            ->options([
                                'pending' => 'Pendente',
                                'in_progress' => 'Em Progresso',
                                'completed' => 'Concluída',
                                'cancelled' => 'Cancelada',
                            ])
                            ->required()
                            ->default('pending'),

                        Forms\Components\DateTimePicker::make('due_date')
                            ->label('Data de Vencimento')
                            ->native(false),
                    ])
                    ->columns(2),
            ]);
    }

    public static function table(Tables\Table $table): Tables\Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('title')
                    ->label('Título')
                    ->searchable()
                    ->sortable(),

                Tables\Columns\TextColumn::make('person.name')
                    ->label('Cliente')
                    ->searchable()
                    ->sortable(),

                Tables\Columns\TextColumn::make('assignedUser.name')
                    ->label('Atribuído a')
                    ->searchable(),

                Tables\Columns\BadgeColumn::make('priority')
                    ->label('Prioridade')
                    ->colors([
                        'success' => 'low',
                        'warning' => 'medium',
                        'danger' => 'high',
                        'danger' => 'urgent',
                    ]),

                Tables\Columns\BadgeColumn::make('status')
                    ->label('Status')
                    ->colors([
                        'gray' => 'pending',
                        'warning' => 'in_progress',
                        'success' => 'completed',
                        'danger' => 'cancelled',
                    ]),

                Tables\Columns\TextColumn::make('due_date')
                    ->label('Vencimento')
                    ->dateTime('d/m/Y H:i')
                    ->sortable(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('status')
                    ->label('Status')
                    ->options([
                        'pending' => 'Pendente',
                        'in_progress' => 'Em Progresso',
                        'completed' => 'Concluída',
                        'cancelled' => 'Cancelada',
                    ]),

                Tables\Filters\SelectFilter::make('priority')
                    ->label('Prioridade')
                    ->options([
                        'low' => 'Baixa',
                        'medium' => 'Média',
                        'high' => 'Alta',
                        'urgent' => 'Urgente',
                    ]),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }

    public static function getPages(): array
    {
        return [
            'index' => Pages\ListTasks::route('/'),
            'create' => Pages\CreateTask::route('/create'),
            'edit' => Pages\EditTask::route('/{record}/edit'),
        ];
    }
}

3. Criar Widget para Dashboard

bash
php artisan make:filament-widget TasksOverview
php
// app/Filament/Widgets/TasksOverview.php
namespace App\Filament\Widgets;

use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use App\Models\Task;

class TasksOverview extends BaseWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Tarefas Pendentes', Task::where('status', 'pending')->count())
                ->description('Aguardando início')
                ->descriptionIcon('heroicon-m-clock')
                ->color('warning'),

            Stat::make('Em Progresso', Task::where('status', 'in_progress')->count())
                ->description('Sendo executadas')
                ->descriptionIcon('heroicon-m-arrow-path')
                ->color('info'),

            Stat::make('Concluídas', Task::where('status', 'completed')->count())
                ->description('Finalizadas')
                ->descriptionIcon('heroicon-m-check-circle')
                ->color('success'),
        ];
    }
}

4. Criar Eventos

php
// app/Events/TaskCreated.php
namespace App\Events;

use App\Models\Task;
use Illuminate\Foundation\Events\Dispatchable;

class TaskCreated
{
    use Dispatchable;

    public function __construct(
        public readonly Task $task
    ) {}
}
php
// app/Listeners/NotifyAssignedUser.php
namespace App\Listeners;

use App\Events\TaskCreated;
use Filament\Notifications\Notification;

class NotifyAssignedUser
{
    public function handle(TaskCreated $event): void
    {
        if ($event->task->assignedUser) {
            Notification::make()
                ->title('Nova tarefa atribuída')
                ->body("Tarefa: {$event->task->title}")
                ->sendToDatabase($event->task->assignedUser);
        }
    }
}

Registrar no EventServiceProvider:

php
protected $listen = [
    TaskCreated::class => [
        NotifyAssignedUser::class,
    ],
];

Customizar Módulos Existentes

Adicionar Campo ao People Resource

php
// app/Filament/Resources/PersonResource.php

public static function form(Forms\Form $form): Forms\Form
{
    return $form
        ->schema([
            // Campos existentes...

            // Novo campo customizado
            Forms\Components\Select::make('customer_type')
                ->label('Tipo de Cliente')
                ->options([
                    'vip' => 'VIP',
                    'regular' => 'Regular',
                    'premium' => 'Premium',
                ])
                ->default('regular'),

            Forms\Components\TextInput::make('tax_id')
                ->label('Inscrição Estadual')
                ->mask('999.999.999.999')
                ->maxLength(15),
        ]);
}

Não esqueça de adicionar a migration:

bash
php artisan make:migration add_custom_fields_to_people_table
php
Schema::table('people', function (Blueprint $table) {
    $table->string('customer_type')->default('regular');
    $table->string('tax_id')->nullable();
});

Sobrescrever Lógica de Invoice

php
// app/Models/Invoice.php

class Invoice extends BaseInvoice
{
    // Sobrescrever cálculo de impostos
    public function calculateTaxes(): array
    {
        $taxes = parent::calculateTaxes();

        // Adicionar ISS para serviços
        if ($this->type === 'service') {
            $taxes['iss'] = [
                'rate' => 5.00,
                'value' => $this->subtotal * 0.05,
            ];
        }

        return $taxes;
    }
}

Integrar APIs Externas

Exemplo: Integração com API de CEP

bash
composer require eduardokum/laravel-correios
php
// app/Services/AddressService.php
namespace App\Services;

use Eduardokum\LaravelCorreios\Consultas\CEP;

class AddressService
{
    public function findByCep(string $cep): ?array
    {
        try {
            $result = CEP::consulta($cep);

            return [
                'street' => $result->getLogradouro(),
                'neighborhood' => $result->getBairro(),
                'city' => $result->getCidade(),
                'state' => $result->getUf(),
                'zipcode' => $result->getCep(),
            ];
        } catch (\Exception $e) {
            return null;
        }
    }
}

Usar no Form:

php
Forms\Components\TextInput::make('zipcode')
    ->label('CEP')
    ->mask('99999-999')
    ->reactive()
    ->afterStateUpdated(function ($state, callable $set) {
        if (strlen($state) === 9) {
            $service = new AddressService();
            $address = $service->findByCep($state);

            if ($address) {
                $set('street', $address['street']);
                $set('neighborhood', $address['neighborhood']);
                $set('city', $address['city']);
                $set('state', $address['state']);
            }
        }
    }),

Exemplo: Integração com Gateway de Pagamento

bash
composer require stripe/stripe-php
php
// app/Services/PaymentService.php
namespace App\Services;

use Stripe\StripeClient;
use App\Models\Invoice;

class PaymentService
{
    protected StripeClient $stripe;

    public function __construct()
    {
        $this->stripe = new StripeClient(config('services.stripe.secret'));
    }

    public function createPaymentIntent(Invoice $invoice): string
    {
        $intent = $this->stripe->paymentIntents->create([
            'amount' => $invoice->total * 100, // Centavos
            'currency' => 'brl',
            'metadata' => [
                'invoice_id' => $invoice->id,
            ],
        ]);

        return $intent->client_secret;
    }

    public function handleWebhook(array $payload): void
    {
        if ($payload['type'] === 'payment_intent.succeeded') {
            $invoiceId = $payload['data']['object']['metadata']['invoice_id'];
            $invoice = Invoice::find($invoiceId);
            $invoice->markAsPaid();
        }
    }
}

Criar Actions Customizadas

Action para Duplicar Fatura

php
// app/Filament/Resources/InvoiceResource.php

Tables\Actions\Action::make('duplicate')
    ->label('Duplicar')
    ->icon('heroicon-o-document-duplicate')
    ->action(function (Invoice $record) {
        $newInvoice = $record->replicate();
        $newInvoice->number = Invoice::generateNumber();
        $newInvoice->status = 'draft';
        $newInvoice->save();

        // Duplicar itens
        foreach ($record->items as $item) {
            $newItem = $item->replicate();
            $newItem->invoice_id = $newInvoice->id;
            $newItem->save();
        }

        Notification::make()
            ->title('Fatura duplicada com sucesso')
            ->success()
            ->send();

        return redirect()->route('filament.resources.invoices.edit', $newInvoice);
    })
    ->requiresConfirmation(),

Bulk Action Customizada

php
Tables\Actions\BulkAction::make('markAsPaid')
    ->label('Marcar como Pago')
    ->icon('heroicon-o-check-circle')
    ->action(function (Collection $records) {
        $records->each->markAsPaid();

        Notification::make()
            ->title("{$records->count()} faturas marcadas como pagas")
            ->success()
            ->send();
    })
    ->requiresConfirmation()
    ->color('success'),

Adicionar Middleware Customizado

bash
php artisan make:middleware CheckSubscription
php
// app/Http/Middleware/CheckSubscription.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckSubscription
{
    public function handle(Request $request, Closure $next)
    {
        $user = $request->user();

        if (!$user->hasActiveSubscription()) {
            return redirect()->route('subscription.expired');
        }

        return $next($request);
    }
}

Registrar no Kernel.php:

php
protected $routeMiddleware = [
    // ...
    'subscription' => \App\Http\Middleware\CheckSubscription::class,
];

Aplicar ao Filament:

php
// app/Providers/FilamentServiceProvider.php
Filament::serving(function () {
    Filament::registerMiddleware([
        \App\Http\Middleware\CheckSubscription::class,
    ]);
});

Criar Command Artisan Customizado

bash
php artisan make:command SendInvoiceReminders
php
// app/Console/Commands/SendInvoiceReminders.php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\Invoice;
use App\Models\Communication;

class SendInvoiceReminders extends Command
{
    protected $signature = 'invoices:send-reminders';
    protected $description = 'Enviar lembretes para faturas vencendo em 3 dias';

    public function handle(): int
    {
        $invoices = Invoice::where('status', 'pending')
            ->whereBetween('due_date', [now(), now()->addDays(3)])
            ->get();

        $this->info("Encontradas {$invoices->count()} faturas para lembrete");

        foreach ($invoices as $invoice) {
            Communication::create([
                'person_id' => $invoice->person_id,
                'channel' => 'email',
                'subject' => 'Lembrete: Fatura vencendo',
                'message' => "Sua fatura {$invoice->number} vence em 3 dias.",
            ])->send();

            $this->info("Lembrete enviado para: {$invoice->person->name}");
        }

        return Command::SUCCESS;
    }
}

Agendar no Kernel.php:

php
protected function schedule(Schedule $schedule): void
{
    $schedule->command('invoices:send-reminders')
        ->daily()
        ->at('09:00');
}

Customizar Tema do Filament

Personalizar Cores

php
// config/filament.php
'theme' => [
    'colors' => [
        'primary' => '#6366f1',     // Indigo
        'secondary' => '#64748b',   // Slate
        'success' => '#22c55e',     // Green
        'warning' => '#f59e0b',     // Amber
        'danger' => '#ef4444',      // Red
        'info' => '#3b82f6',        // Blue
    ],
],

CSS Customizado

css
/* resources/css/filament.css */
:root {
    --sidebar-width: 280px;
}

.filament-sidebar {
    background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%);
}

.filament-brand {
    font-size: 1.5rem;
    font-weight: 700;
    background: linear-gradient(90deg, #6366f1, #8b5cf6);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

Compilar:

bash
npm run build

Próximos Passos

  1. 🏗️ Revise a Estrutura do Projeto
  2. ⚙️ Configure Variáveis de Ambiente
  3. 🚀 Explore Funcionalidades Principais
  4. 🚢 Prepare o Deploy em Produção

Recursos Úteis

Documentação privada do ecossistema Filament Core.