core.md
Controllers, Eloquent, Blade conventions.
Framework guidelines
Laravel framework conventions — Eloquent, Blade, testing, Filament. Read it here, or pipe the address through a shell to install it into your project.
$ curl https://agents.c11.dev/laravel | sh
Read before you run: drop | sh to print the script,
or open
the raw installer.
It pulls
php first, then the
laravel files below.
core.md
Controllers, Eloquent, Blade conventions.
testing.md
Pest test conventions and pre-test checks.
filament.md optional
Filament v4 resource/test conventions.
Optional (filament): the installer asks
about it
on install. Non-interactively, add a specific one with
--with <name> — e.g.
curl https://agents.c11.dev/laravel | sh -s -- --with filament
— or --all for every optional.
#[ObservedBy([UserObserver::class])] with use Illuminate\Database\Eloquent\Attributes\ObservedBy; on topuse section classes. Examples: use auth()->id() instead of Auth::id() and adding Auth in the use section. Other examples: use redirect()->route() instead of Redirect::route(), or str()->slug() instead of Str::slug().whereKey() or whereKeyNot(), use specific fields like id. Example: instead of ->whereKeyNot($currentUser->getKey()), use ->where('id', '!=', $currentUser->id).::query() when running Eloquent create() statements. Example: instead of User::query()->create(), use User::create().$fillable array to include those new attributes.make:model -m, make:migration) with && or ; — they may get identical timestamps. Run each command separately and wait for completion before running the next.->value) instead of raw strings everywhere — routes, middleware, migrations, seeds, configs, and UI defaults.__invoke(); multi-method RESTful controllers should use Route::resource()->only([])view(). Instead, use Route::view() with Blade file directly.@selected() and @checked() directives instead of selected and checked HTML attributes. Good example: @selected(old(‘status’) === App\Enums\ProjectStatus::Pending->value). Bad example: {{ old(‘status’) === App\Enums\ProjectStatus::Pending->value ? ‘selected’ : ‘’ }}.app/Enums, not in the main app/ folder, unless instructed differently.## Laravel instructions
- Using Services in Controllers: if Service class is used only in ONE method of Controller, inject it directly into that method with type-hinting. If Service class is used in MULTIPLE methods of Controller, initialize it in Constructor.
- **Eloquent Observers** should be registered in Eloquent Models with PHP Attributes, and not in AppServiceProvider. Example: `#[ObservedBy([UserObserver::class])]` with `use Illuminate\Database\Eloquent\Attributes\ObservedBy;` on top
- Aim for "slim" Controllers and put larger logic pieces in Service classes
- Use Laravel helpers instead of `use` section classes. Examples: use `auth()->id()` instead of `Auth::id()` and adding `Auth` in the `use` section. Other examples: use `redirect()->route()` instead of `Redirect::route()`, or `str()->slug()` instead of `Str::slug()`.
- Don't use `whereKey()` or `whereKeyNot()`, use specific fields like `id`. Example: instead of `->whereKeyNot($currentUser->getKey())`, use `->where('id', '!=', $currentUser->id)`.
- Don't add `::query()` when running Eloquent `create()` statements. Example: instead of `User::query()->create()`, use `User::create()`.
- In Livewire projects, don't use Livewire Volt. Only Livewire class components.
- When adding columns in a migration, update the model's `$fillable` array to include those new attributes.
- Never chain multiple migration-creating commands (e.g., `make:model -m`, `make:migration`) with `&&` or `;` — they may get identical timestamps. Run each command separately and wait for completion before running the next.
- Enums: If a PHP Enum exists for a domain concept, always use its cases (or their `->value`) instead of raw strings everywhere — routes, middleware, migrations, seeds, configs, and UI defaults.
- Controllers: Single-method Controllers should use `__invoke()`; multi-method RESTful controllers should use `Route::resource()->only([])`
- Don't create Controllers with just one method which just returns `view()`. Instead, use `Route::view()` with Blade file directly.
- Always use Laravel's @session() directive instead of @if(session()) for displaying flash messages in Blade templates.
- In Blade files always use `@selected()` and `@checked()` directives instead of `selected` and `checked` HTML attributes. Good example: @selected(old('status') === App\Enums\ProjectStatus::Pending->value). Bad example: {{ old('status') === App\Enums\ProjectStatus::Pending->value ? 'selected' : '' }}.
- Generate Enums always in the folder `app/Enums`, not in the main `app/` folder, unless instructed differently.
- Always use Enum value as the default in the migration if column values are from the enum. Always casts this column to the enum type in the Model.
- Always use Enum where possible instead of hardcoded string values, if Enum class exists. For example, in Blade files, and in the tests when creating data if field is casted to Enum then use that Enum instead of hardcoding the value.
- For library documentation, if some library is not available in Laravel Boost 'search-docs', always use context7. Automatically use the Context7 MCP tools to resolve library id and get library docs without me having to explicitly ask.
Check database schema - Use database-schema tool to understand:
Verify relationship names - Read the model file to confirm:
Test realistic states - Don’t assume:
user_id foreign key = user() relationship (could be author(), employer(), etc.)assertSessionHasOldInput().## Testing instructions
- For new features, you MUST generate Pest automated tests.
### Before Writing Tests
1. **Check database schema** - Use `database-schema` tool to understand:
- Which columns have defaults
- Which columns are nullable
- Foreign key relationship names
2. **Verify relationship names** - Read the model file to confirm:
- Exact relationship method names (not assumed from column names)
- Return types and related models
3. **Test realistic states** - Don't assume:
- Empty model = all nulls (check for defaults)
- `user_id` foreign key = `user()` relationship (could be `author()`, `employer()`, etc.)
- When testing form submissions that redirect back with errors, assert that old input is preserved using `assertSessionHasOldInput()`.
getUrl() instead of Laravel route(). Instead of route('filament.admin.resources.class-schedules.index'), use ClassScheduleResource::getUrl('index'). Also, specify the exacy Resource name, instead of getResource().Livewire::test(class) and not livewire(class), to avoid extra dependency on pestphp/pest-plugin-livewire.HasLabel, HasColor and HasIcon interfaces if aren’t added yet instead of specifying values/labels/colors/icons inside of Filament Forms/Tables. CRITICAL: Always use the exact return type declarations from the interface definitions - do NOT substitute specific types (e.g., use string|BackedEnum|Htmlable|null for getIcon(), not string|Heroicon|null). When defining a default using enum never add ->value. Refer to this docs page: https://filamentphp.com/docs/4.x/advanced/enums->authorize('ability') method on the action instead of manually calling Gate::authorize() or checking Gate::allows(). The authorize() method handles both authorization enforcement and action visibility automatically.unique() has ignoreRecord: true by default, no need to specify it.->form() on Actions/Filters → use ->schema() instead->mutateFormDataUsing() → use ->mutateDataUsing() insteadPlaceholder::make() → use TextEntry::make()->state() instead (import from Filament\Infolists\Components\TextEntry)->label('') for hidden labels → use ->hiddenLabel() instead## Filament Rules
- When generating Filament resource, you MUST generate Filament smoke tests to check if the Resource works. When making changes to Filament resource, you MUST run the tests (generate them if they don't exist) and make changes to resource/tests to make the tests pass.
- When generating Filament resource, don't generate View page or Infolist, unless specifically instructed.
- When referencing the Filament routes, aim to use `getUrl()` instead of Laravel `route()`. Instead of `route('filament.admin.resources.class-schedules.index')`, use `ClassScheduleResource::getUrl('index')`. Also, specify the exacy Resource name, instead of `getResource()`.
- When writing tests with Pest, use syntax `Livewire::test(class)` and not `livewire(class)`, to avoid extra dependency on `pestphp/pest-plugin-livewire`.
- When using Enum class for Eloquent Model field, add Enum `HasLabel`, `HasColor` and `HasIcon` interfaces if aren't added yet instead of specifying values/labels/colors/icons inside of Filament Forms/Tables. **CRITICAL**: Always use the exact return type declarations from the interface definitions - do NOT substitute specific types (e.g., use `string|BackedEnum|Htmlable|null` for `getIcon()`, not `string|Heroicon|null`). When defining a default using enum never add `->value`. Refer to this docs page: https://filamentphp.com/docs/4.x/advanced/enums
- Always use Enum instead of hardcoded string value where possible, if Enum class exists. For example, in the tests, when creating data, if field is casted to Enum, then use that Enum instead of hardcoded string value.
- When adding icons, always use the Filament enum Filament\Support\Icons\Heroicon class instead of string.
- When adding actions that require authorization, use the `->authorize('ability')` method on the action instead of manually calling `Gate::authorize()` or checking `Gate::allows()`. The `authorize()` method handles both authorization enforcement and action visibility automatically.
- In Filament v4, validation rule `unique()` has `ignoreRecord: true` by default, no need to specify it.
- In Filament v4, if you create custom Blade files with Tailwind classes, you need to create a custom theme and specify the folder of those Blade files in theme.css.
- **Deprecated v3 methods - do NOT use:**
- `->form()` on Actions/Filters → use `->schema()` instead
- `->mutateFormDataUsing()` → use `->mutateDataUsing()` instead
- `Placeholder::make()` → use `TextEntry::make()->state()` instead (import from `Filament\Infolists\Components\TextEntry`)
- `->label('')` for hidden labels → use `->hiddenLabel()` instead