Skip to content

Windsurf Rules for Laravel Projects

Windsurf.windsurfrulesCoding

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.

@library0 upvotes0 remixes0 comments

Get this skill

Add the skill file with the CLI, by hand, or over MCP.

Save this to your project or home directory so Windsurf can load it.

npx goodprompts add windsurf-laravel-conventions
Saves to./.windsurfrules
npx goodprompts add windsurf-laravel-conventions
Saves to ./.windsurfrules

Skill file

.windsurfrules2,654 characters
# 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/`.
Comments

Comments

Sign in to commentSign in

Remixes

Loading remixes…