Windsurf Rules for Laravel Projects
Laravel conventions that keep controllers thin: Form Requests for all validation, no Eloquent queries outside models and actions, queued jobs for anything slow, Pest for tests.
Use when: Use in a Laravel 11+ application when you want Windsurf to follow framework idioms instead of writing raw queries in controllers or validating inline.
The skill file
Windsurf · .windsurfrules.windsurfrules2654 chars
# Laravel Project Rules
## Eloquent discipline
- No Eloquent or query-builder calls in controllers, commands, or Blade views. Queries belong in the model (scopes), a dedicated Action class, or a query class under `app/Queries/`.
- Always eager-load relations you iterate over. If a loop touches `$model->relation`, the calling query needs `with()`. Treat any new N+1 as a bug, and keep `Model::preventLazyLoading()` enabled in `AppServiceProvider`.
- `$fillable` is required on every model; never use `$guarded = []`.
- Use database transactions (`DB::transaction`) around any action that writes to more than one table.
- Casts go in the `casts()` method; never store JSON in a string column without an `array`/`AsCollection` cast.
- Never call `Model::all()` on user-facing paths — paginate or chunk.
## HTTP layer
- Every POST/PUT/PATCH/DELETE endpoint validates via a Form Request class. Inline `$request->validate()` is forbidden.
- Authorization lives in the Form Request's `authorize()` or a Policy — never `if ($user->id !== ...)` checks in controllers.
- Controllers contain at most: resolve input, call one action, return a response. If a controller method exceeds ~15 lines, extract an Action.
- API responses go through `JsonResource` classes; never return raw models from API controllers.
## Jobs and queues
- Anything that sends mail, calls an external API, or takes longer than ~100ms goes in a queued job — never inline in the request cycle.
- Every job declares explicit `$tries` and `$backoff`, and implements `failed()` to log context.
- Jobs must be idempotent: guard with `WithoutOverlapping` or a unique key, and assume the queue may deliver twice.
- Pass model IDs to jobs, then refetch inside `handle()` — never serialize whole loaded relations.
- New jobs are dispatched via `dispatch()` from an Action, not from controllers directly.
## Pest tests
- All new tests use Pest syntax (`it(...)`, `expect(...)`), not PHPUnit classes.
- Feature tests hit real routes with `RefreshDatabase`; do not mock Eloquent.
- Every Form Request gets a test for one valid payload and each validation rule that can fail.
- Use factories for all test data; never insert via raw arrays or seeders inside tests.
- Queued side effects are asserted with `Queue::fake()` / `Mail::fake()`, not by running the job inline.
## Workflow
- Run `./vendor/bin/pint` and `php artisan test` before suggesting a commit.
- New migrations are additive in production paths — never edit a migration that has already been merged; add a new one.
- Config values are read via `config()` from a config file backed by `env()`; never call `env()` outside `config/`.
Install
drop into your repoSave this to your project or home directory so Windsurf can load it.
path./.windsurfrules
Discussion
What people are saying
Forks
0 forks of this skillLoading forks…