From laravel
The house rules for building Filament v5 panels — schema layout, navigation grouping, relation managers, save-redirects, delete placement, the user-menu profile/settings page, native authentication + optional authenticator-app (TOTP) 2FA, and no public registration. Opinionated, prescriptive, and grounded in real Filament 5.6 source so every namespace and method is correct. Trigger: load whenever editing Laravel/Filament code — creating or changing a Filament Resource, form/table schema, page (Create/Edit/List), RelationManager, or PanelProvider; wiring panel auth/2FA; or scaffolding admin UI with `make:filament-*`.
How this skill is triggered — by the user, by Claude, or both
Slash command
/laravel:filament-conventionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Verified against **Filament v5.6.7** (`filament/filament` + `filament/schemas`). Every class,
Verified against Filament v5.6.7 (filament/filament + filament/schemas). Every class,
namespace, and method below was read from vendor source — do not substitute from memory or from
v3 habits. The full worked recipe (copy-paste examples + the vendor file:symbol that grounds
each claim) is in references/filament-5-recipes.md.
Filament\Forms\Components\* (e.g. TextInput, Select, KeyValue).Filament\Actions\* (e.g. Action, CreateAction, EditAction, DeleteAction,
DeleteBulkAction, BulkActionGroup, AttachAction, DetachAction).Filament\Schemas\Components\* (e.g. Section, Fieldset, Grid,
Tabs). The schema container itself is Filament\Schemas\Schema.In v5 a Resource form is a Schema, not the old v3 Form:
public static function form(Schema $schema): Schema. The top-level schema takes
->components([...]); layout components (Section, Fieldset) take ->schema([...]).
php artisan make:filament-resource Contact --generate # form, table, pages, schema dirs
php artisan make:filament-relation-manager ContactResource tags name
php artisan make:filament-page Settings
php artisan make:filament-user # seed an admin (no public registration)
v5 splits a resource into Schemas/<Model>Form.php, Tables/<Model>Table.php, and
Pages/. Edit those generated files — do not relocate the logic back into the resource class.
Organize every form schema with Section and Fieldset first. Start from grouped
layout, then drop fields in — never a flat bag of inputs.
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Fieldset;
use Filament\Forms\Components\TextInput;
$schema->components([
Section::make('Identity')
->description('Who this contact is')
->columns(2)
->schema([
TextInput::make('first_name')->required(),
TextInput::make('last_name'),
]),
Fieldset::make('Contact') // two-column grid by default
->schema([
TextInput::make('email')->email()->required(),
TextInput::make('phone')->tel(),
]),
]);
Group pages/resources into navigation menu groups. Set the static property; its type is
string | UnitEnum | null, so prefer a backed enum for type-safe, reusable group names.
use UnitEnum;
protected static string | UnitEnum | null $navigationGroup = 'CRM';
Provide a RelationManager for every real relation. Register them in the resource's
getRelations(); each manager declares protected static string $relationship = '...' and
its own form(Schema $schema) + table(Table $table).
public static function getRelations(): array
{
return [TagsRelationManager::class, ActivitiesRelationManager::class];
}
Delete belongs on the LIST table, not the Edit page. make:filament-resource puts
DeleteAction::make() in the Edit page's getHeaderActions() by default — remove it.
Put deletion in the table: DeleteAction::make() in ->recordActions([...]) (per-row) and
DeleteBulkAction::make() inside a BulkActionGroup in ->toolbarActions([...]).
v5 table methods are ->recordActions() / ->toolbarActions(); v3's ->actions() /
->bulkActions() still exist as @deprecated aliases that forward to them — prefer the new names.
Create and Edit both redirect to the LIST page on save. Two ways:
->resourceCreatePageRedirect('index')->resourceEditPageRedirect('index').getRedirectUrl() to return $this->getResource()::getUrl('index').
Default Filament redirects to view/edit, so this override is required.The top-bar user menu must have a Settings/profile page. Register a profile page on the
panel — ->profile() — and Filament auto-injects the profile item into the user menu. Use a
custom page (->profile(Settings::class)) when you need more than the default account form.
Native Filament auth + OPTIONAL authenticator-app (TOTP) 2FA. Enable login with
->login(). For app-based 2FA the user can turn on themselves, register the
AppAuthentication provider with isRequired: false:
use Filament\Auth\MultiFactor\App\AppAuthentication;
$panel->multiFactorAuthentication([
AppAuthentication::make()->recoverable(),
]); // 3rd arg isRequired defaults to false → optional
The User model must implement Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication
(and HasAppAuthenticationRecovery for recovery codes). The enable/disable + QR-code UI is
served by the profile page from rule 6 — no custom security page to build.
No public registration. Simply do not call ->registration() on the panel. Seed users
with php artisan make:filament-user or your own invite flow.
The dashboard is a custom page — never the stock one. Filament's default
Filament\Pages\Dashboard auto-discovers and arranges widgets for you, which costs you control.
Generate your own page extending the base Dashboard so you decide exactly which widgets show,
in what order, and the column layout. Override getWidgets() and getColumns() (it accepts a
responsive array); getHeaderWidgets()/getFooterWidgets() for the top/bottom slots.
// php artisan make:filament-page Dashboard (then extend the base, not a blank Page)
namespace App\Filament\Pages;
use Filament\Pages\Dashboard as BaseDashboard;
class Dashboard extends BaseDashboard
{
public function getWidgets(): array
{
return [StatsOverview::class, RevenueChart::class, LatestContacts::class];
}
public function getColumns(): int | array
{
return ['md' => 2, 'xl' => 3]; // explicit, responsive widget grid
}
}
Register the custom page on the panel — ->pages([\App\Filament\Pages\Dashboard::class]) — and
make sure the stock Filament\Pages\Dashboard::class is not also registered (page
discovery + an explicit entry can yield two dashboards). Grounded in Filament\Pages\Dashboard
(vendor/filament/filament/src/Pages/Dashboard.php: class Dashboard extends Page; getWidgets(),
getColumns(): int | array).
If the Resource/panel you're editing lives in a symlinked composer package (per the repo's
package rules), make the change in that package's own folder and commit/push there — never
patch the consumer's vendor/ copy.
Section/Fieldset, not loose fields (rule 1).DeleteAction (rule 4); the table has per-row + bulk delete.->login(), a profile page, optional AppAuthentication, and no ->registration().BaseDashboard with explicit getWidgets()/getColumns(), and the stock Filament\Pages\Dashboard is not double-registered (rule 9).See references/filament-5-recipes.md for a full panel + resource example and the source
citations behind each rule.
npx claudepluginhub mwguerra/plugins --plugin laravelBlocks Edit/Write/Bash actions until Claude investigates importers, data schemas, and user instructions. Improves output quality by forcing concrete facts before edits.