.cursor/skills/laravel-best-practices/SKILL.md
Best practices for Laravel, prioritized by impact. Each rule teaches what to do and why. For exact API syntax, verify with search-docs.
Before applying any rule, check what the application already does. Laravel offers multiple valid approaches — the best choice is the one the codebase already uses, even if another pattern would be theoretically better. Inconsistency is worse than a suboptimal pattern.
Check sibling files, related controllers, models, or tests for established patterns. If one exists, follow it — don't introduce a second way. These rules are defaults for when no pattern exists yet, not overrides.
rules/db-performance.mdwith() to prevent N+1 queriesModel::preventLazyLoading() in developmentSELECT *chunk() / chunkById() for large datasetsWHERE, ORDER BY, JOINwithCount() instead of loading relations to countcursor() for memory-efficient read-only iterationrules/advanced-queries.mdaddSelect() subqueries over eager-loading entire has-many for a single valuebelongsToCASE WHEN in selectRaw) over multiple count queriessetRelation() to prevent circular N+1 querieswhereIn + pluck() over whereHas for better index usageorderBy column orderorderBy for has-many sorting (avoid joins)rules/security.md$fillable or $guarded on every model, authorize every action via policies or gates{{ }} for output escaping, @csrf on all POST/PUT/DELETE forms, throttle on auth and API routes.env, use config() for secrets, encrypted cast for sensitive DB fieldsrules/caching.mdCache::remember() over manual get/putCache::flexible() for stale-while-revalidate on high-traffic dataCache::memo() to avoid redundant cache hits within a requestCache::add() for atomic conditional writesonce() to memoize per-request or per-object lifetimeCache::lock() / lockForUpdate() for race conditionsrules/eloquent.mdcasts() methodwhereBelongsTo($model) for cleaner queries(new Model)->getTable() or Eloquent queriesrules/validation.md['required', 'email'] for new code; follow existing convention$request->validated() only — never $request->all()Rule::when() for conditional validationafter() instead of withValidator()rules/config.mdenv() only inside config filesApp::environment() or app()->isProduction()rules/testing.mdLazilyRefreshDatabase over RefreshDatabase for speedassertModelExists() over raw assertDatabaseHas()Event::fake(), Exceptions::fake(), etc.) — but always after factory setup, not beforerecycle() to share relationship instances across factoriesrules/queue-jobs.mdretry_after must exceed job timeout; use exponential backoff [1, 5, 10]ShouldBeUnique to prevent duplicates; ShouldBeUniqueUntilProcessing for early lock releasefailed(); with retryUntil(), set $tries = 0RateLimited middleware for external API calls; Bus::batch() for related jobsrules/routing.mdRoute::resource() or apiResource()rules/http-client.mdtimeout and connectTimeout on every requestretry() with exponential backoff for external APIsthrow()Http::pool() for concurrent independent requestsHttp::fake() and preventStrayRequests() in testsrules/events-notifications.md, rules/mail.mdevent:cache in productionShouldDispatchAfterCommit / afterCommit() inside transactionsShouldQueueHasLocalePreference on notifiable modelsassertQueued() not assertSent() for queued mailablesrules/error-handling.mdreport()/render() on exception classes or in bootstrap/app.php — follow existing patternShouldntReport for exceptions that should never logdontReportDuplicates() for multi-catch scenarioscontext() on exception classesrules/scheduling.mdwithoutOverlapping() on variable-duration tasksonOneServer() on multi-server deploymentsrunInBackground() for concurrent long tasksenvironments() to restrict to appropriate environmentstakeUntilTimeout() for time-bounded processingrules/architecture.mdapp() helperORDER BY id DESC or created_at DESC; mb_* for UTF-8 safetydefer() for post-response work; Context for request-scoped data; Concurrency::run() for parallel executionrules/migrations.mdphp artisan make:migrationconstrained() for foreign keys$attributesdown() by default; forward-fix migrations for intentionally irreversible changesrules/collections.mdcursor() vs. lazy() — choose based on relationship needslazyById() when updating records while iteratingtoQuery() for bulk operations on collectionsrules/blade-views.md$attributes->merge() in component templates@include; @pushOnce for per-component scripts@aware for deeply nested component propsrules/style.mdStr, Arr, Number, Uri, Str::of(), $request->string()) over raw PHP functionsAlways use a sub-agent to read rule files and explore this skill's content.
search-docs for the installed Laravel version