Laravel Best Practices
Best practices for Laravel, prioritized by impact. Each rule teaches what to do and why. For exact API syntax, verify with search-docs.
Consistency First
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.
Quick Reference
1. Database Performance → rules/db-performance.md
- Eager load with
with()to prevent N+1 queries - Enable
Model::preventLazyLoading()in development - Select only needed columns, avoid
SELECT * chunk()/chunkById()for large datasets- Index columns used in
WHERE,ORDER BY,JOIN withCount()instead of loading relations to countcursor()for memory-efficient read-only iteration- Never query in Blade templates
2. Advanced Query Patterns → rules/advanced-queries.md
addSelect()subqueries over eager-loading entire has-many for a single value- Dynamic relationships via subquery FK +
belongsTo - Conditional aggregates (
CASE WHENinselectRaw) over multiple count queries setRelation()to prevent circular N+1 querieswhereIn+pluck()overwhereHasfor better index usage- Two simple queries can beat one complex query
- Compound indexes matching
orderBycolumn order - Correlated subqueries in
orderByfor has-many sorting (avoid joins)
3. Security → rules/security.md
- Define
$fillableor$guardedon every model, authorize every action via policies or gates - No raw SQL with user input — use Eloquent or query builder
{{ }}for output escaping,@csrfon all POST/PUT/DELETE forms,throttleon auth and API routes- Validate MIME type, extension, and size for file uploads
- Never commit
.env, useconfig()for secrets,encryptedcast for sensitive DB fields
4. Caching → rules/caching.md
Cache::remember()over manual get/putCache::flexible()for stale-while-revalidate on high-traffic dataCache::memo()to avoid redundant cache hits within a request- Cache tags to invalidate related groups
Cache::add()for atomic conditional writesonce()to memoize per-request or per-object lifetimeCache::lock()/lockForUpdate()for race conditions- Failover cache stores in production
5. Eloquent Patterns → rules/eloquent.md
- Correct relationship types with return type hints
- Local scopes for reusable query constraints
- Global scopes sparingly — document their existence
- Attribute casts in the
casts()method - Cast date columns, use Carbon instances in templates
whereBelongsTo($model)for cleaner queries- Never hardcode table names — use
(new Model)->getTable()or Eloquent queries
6. Validation & Forms → rules/validation.md
- Form Request classes, not inline validation
- Array notation
['required', 'email']for new code; follow existing convention $request->validated()only — never$request->all()Rule::when()for conditional validationafter()instead ofwithValidator()
7. Configuration → rules/config.md
env()only inside config filesApp::environment()orapp()->isProduction()- Config, lang files, and constants over hardcoded text
8. Testing Patterns → rules/testing.md
LazilyRefreshDatabaseoverRefreshDatabasefor speedassertModelExists()over rawassertDatabaseHas()- Factory states and sequences over manual overrides
- Use fakes (
Event::fake(),Exceptions::fake(), etc.) — but always after factory setup, not before recycle()to share relationship instances across factories
9. Queue & Job Patterns → rules/queue-jobs.md
retry_aftermust exceed jobtimeout; use exponential backoff[1, 5, 10]ShouldBeUniqueto prevent duplicates;WithoutOverlapping::untilProcessing()for concurrency- Always implement
failed(); withretryUntil(), set$tries = 0 RateLimitedmiddleware for external API calls;Bus::batch()for related jobs- Horizon for complex multi-queue scenarios
10. Routing & Controllers → rules/routing.md
- Implicit route model binding
- Scoped bindings for nested resources
Route::resource()orapiResource()- Methods under 10 lines — extract to actions/services
- Type-hint Form Requests for auto-validation
11. HTTP Client → rules/http-client.md
- Explicit
timeoutandconnectTimeouton every request retry()with exponential backoff for external APIs- Check response status or use
throw() Http::pool()for concurrent independent requestsHttp::fake()andpreventStrayRequests()in tests
12. Events, Notifications & Mail → rules/events-notifications.md, rules/mail.md
- Event discovery over manual registration;
event:cachein production ShouldDispatchAfterCommit/afterCommit()inside transactions- Queue notifications and mailables with
ShouldQueue - On-demand notifications for non-user recipients
HasLocalePreferenceon notifiable modelsassertQueued()notassertSent()for queued mailables- Markdown mailables for transactional emails
13. Error Handling → rules/error-handling.md
report()/render()on exception classes or inbootstrap/app.php— follow existing patternShouldntReportfor exceptions that should never log- Throttle high-volume exceptions to protect log sinks
dontReportDuplicates()for multi-catch scenarios- Force JSON rendering for API routes
- Structured context via
context()on exception classes
14. Task Scheduling → rules/scheduling.md
withoutOverlapping()on variable-duration tasksonOneServer()on multi-server deploymentsrunInBackground()for concurrent long tasksenvironments()to restrict to appropriate environmentstakeUntilTimeout()for time-bounded processing- Schedule groups for shared configuration
15. Architecture → rules/architecture.md
- Single-purpose Action classes; dependency injection over
app()helper - Prefer official Laravel packages and follow conventions, don't override defaults
- Default to
ORDER BY id DESCorcreated_at DESC;mb_*for UTF-8 safety defer()for post-response work;Contextfor request-scoped data;Concurrency::run()for parallel execution
16. Migrations → rules/migrations.md
- Generate migrations with
php artisan make:migration constrained()for foreign keys- Never modify migrations that have run in production
- Add indexes in the migration, not as an afterthought
- Mirror column defaults in model
$attributes - Reversible
down()by default; forward-fix migrations for intentionally irreversible changes - One concern per migration — never mix DDL and DML
17. Collections → rules/collections.md
- Higher-order messages for simple collection operations
cursor()vs.lazy()— choose based on relationship needslazyById()when updating records while iteratingtoQuery()for bulk operations on collections
18. Blade & Views → rules/blade-views.md
$attributes->merge()in component templates- Blade components over
@include;@pushOncefor per-component scripts - View Composers for shared view data
@awarefor deeply nested component props
19. Conventions & Style → rules/style.md
- Follow Laravel naming conventions for all entities
- Prefer Laravel helpers (
Str,Arr,Number,Uri,Str::of(),$request->string()) over raw PHP functions - No JS/CSS in Blade, no HTML in PHP classes
- Code should be readable; comments only for config files
How to Apply
Always use a sub-agent to read rule files and explore this skill's content.
- Identify the file type and select relevant sections (e.g., migration → §16, controller → §1, §3, §5, §6, §10)
- Check sibling files for existing patterns — follow those first per Consistency First
- Verify API syntax with
search-docsfor the installed Laravel version