Generated banner for Laravel Best Practices
Generated logo for Laravel Best Practices

Laravel Best Practices

LaravelOfficialCreated by laravel

  • Skill
  • Guidelines
  • Software development
  • MIT
  • Low risk
  • Has scripts

From the author

Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns.
— the description by laravel

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Laravel best practices provides a structured set of guidelines for writing, reviewing, and refactoring Laravel applications. It emphasizes maintaining consistency with existing codebase patterns while offering standardized approaches for common concerns such as database performance, Eloquent usage, security, validation, and architectural design. The skill acts as a reference index, directing developers to specific rule files based on the task at hand.

What it does

  • Provides a categorized index of rules for various Laravel components.
  • Promotes consistency by prioritizing existing project patterns over new abstractions.
  • Offers guidance on database performance, including eager loading and query optimization.
  • Covers security, validation, routing, and error handling patterns.
  • Advises on architectural decisions, service classes, and job management.
  • Encourages the use of native framework features over custom helpers.

When to use it

  • When writing new controllers, models, or service classes in Laravel.
  • During code reviews to ensure alignment with established framework patterns.
  • When refactoring existing code to improve maintainability or security.
  • When addressing specific issues like N+1 queries or complex Eloquent relationships.
  • When making architectural decisions regarding application structure.

Advantages

  • Ensures architectural consistency across the codebase.
  • Reduces technical debt by discouraging speculative abstractions.
  • Provides clear, domain-specific guidance for common Laravel tasks.
  • Helps prevent common pitfalls like hidden N+1 queries and security vulnerabilities.

Usage examples

“How should I structure my Eloquent queries to avoid N+1 issues?”
The agent will reference the database performance and Eloquent rule files to suggest eager loading strategies and proper relationship definitions.
“Review this controller for security and validation best practices.”
The agent will evaluate the code against the security and validation rule files, suggesting improvements for input handling and authorization.
“Should I create a new helper function for this logic?”
The agent will advise on whether to use existing framework features or if the logic warrants a new service class based on architectural guidelines.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/laravel/laravel-best-practices/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip laravel-best-practices.zip -d .agents/skills

Documentation by laravel

Laravel best practices

Best practices for Laravel, organized as an index of rule files. Each rule file 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 several valid approaches. The best choice is the one the codebase already uses, even if another pattern would be better in theory. Inconsistency is worse than a suboptimal pattern.

Check sibling files and 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.

How to apply

  1. Check the changed files, nearby code, project configuration, and relevant tests for established patterns. Deviate only for a correctness or security defect, and call the deviation out.

  2. Map every affected concern to the rule index below. Read each mapped rule file before editing. Skip unrelated rule files.

  3. Make the smallest coherent change. Keep the application's architecture and naming instead of introducing a second pattern for the same job.

  4. Verify version-sensitive Laravel APIs for the installed version with search-docs, or inspect the installed framework when search-docs is unavailable.

  5. Run the narrowest relevant tests first, then the project's formatting and static-analysis checks when the change warrants them.

  6. Re-read the diff against every mapped rule before finishing.

Rule index

Cross-cutting changes often need more than one rule file.

ConcernRead
Query count, eager loading, indexes, large datasetsrules/db-performance.md
Subqueries, aggregates, complex ordering and query plansrules/advanced-queries.md
Models, relationships, scopes, castsrules/eloquent.md
Authentication, authorization, input safety, secrets, uploadsrules/security.md
Form Requests and validation rulesrules/validation.md
Controllers, route binding, resources, middlewarerules/routing.md
Schema changes, columns, foreign keys, indexesrules/migrations.md
Jobs, retries, uniqueness, batches, Horizonrules/queue-jobs.md
Cache lifetime, invalidation, locks, memoizationrules/caching.md
Outbound requests, retries, timeouts, fakesrules/http-client.md
Exceptions, reporting, rendering, log contextrules/error-handling.md
Events and notificationsrules/events-notifications.md
Mailables and mail assertionsrules/mail.md
Scheduled tasks and overlap protectionrules/scheduling.md
Collections, lazy iteration, bulk operationsrules/collections.md
Blade components, attributes, composersrules/blade-views.md
Environment values and application configurationrules/config.md
Tests: coverage, factories, fakes, and assertionsthe testing-best-practices skill
Naming, helpers, file boundaries, PHP stylerules/style.md
Actions, services, dependencies, application structurerules/architecture.md

Decision rules

  • Prefer framework features and existing application abstractions over new helpers or dependencies.

  • Avoid speculative abstractions. Extract code when it creates a clear domain boundary, removes meaningful duplication, or makes behavior independently testable.

  • Keep database access out of Blade views and prevent hidden N+1 queries across controllers, resources, jobs, and serialization.

Files

rules/architecture.md

Architecture best practices

Extract focused business operations

Extract a discrete business operation into an action class when doing so makes the operation easier to reuse or test. An action class has no special meaning to Laravel. Follow the project's naming and invocation conventions.

class CreateOrderAction
{
    public function __construct(private InventoryService $inventory) {}

    public function handle(array $data): Order
    {
        $order = Order::create($data);
        $this->inventory->reserve($order);

        return $order;
    }
}

Inject required dependencies

Prefer constructor injection for dependencies required throughout an object's lifetime. Use method injection for a dependency that one controller action, listener, job handler, or other container-invoked method needs. Avoid app() and resolve() when normal injection can make a dependency explicit.

Hidden dependency:

class OrderController extends Controller
{
    public function store(StoreOrderRequest $request)
    {
        $service = app(OrderService::class);

        return $service->create($request->validated());
    }
}

Injected dependency:

class OrderController extends Controller
{
    public function store(StoreOrderRequest $request, OrderService $service)
    {
        return $service->create($request->validated());
    }
}

Depend on contracts at boundaries

Depend on contracts at system boundaries, such as payment gateways, notification channels, and external services, when testability or interchangeable implementations justify the abstraction.

Concrete boundary dependency:

class OrderService
{
    public function __construct(private StripeGateway $gateway) {}
}

Contract boundary dependency:

interface PaymentGateway
{
    public function charge(int $amount, string $customerId): PaymentResult;
}

class OrderService
{
    public function __construct(private PaymentGateway $gateway) {}
}

Bind in a service provider:

$this->app->bind(PaymentGateway::class, StripeGateway::class);

Specify a deterministic sort order

Without an explicit ORDER BY, row order is undefined. Choose an order that matches the feature, and add a unique tie-breaker when stable pagination matters.

Unspecified order:

$posts = Post::paginate();

Newest first with a stable tie-breaker:

$posts = Post::query()
    ->orderByDesc('created_at')
    ->orderByDesc('id')
    ->paginate();

Use atomic locks for race conditions

Use a lock when concurrent processes must run one at a time. Cache::lock() provides an atomic lock when the configured cache store supports locks. lockForUpdate() locks selected database rows and must run inside a database transaction. These mechanisms solve different coordination problems. Set the lease (10 below) longer than the callback can ever run. Once the lease expires, another process can acquire the lock while the first process is still working.

Cache::lock('order-processing-'.$order->id, 10)->block(5, function () use ($order) {
    $order->process();
});

// Or at query level, inside a transaction
DB::transaction(function () use ($id) {
    $product = Product::where('id', $id)->lockForUpdate()->first();

    // Read and update the product while the database lock is held.
});

Use mb_* string functions

When no Laravel helper exists, prefer multibyte-aware functions such as mb_strlen() and mb_strtolower() for UTF-8 text. For example, strlen() counts bytes, while strtolower() is not multibyte-aware.

Incorrect:

strlen('José');          // 5 bytes, not 4 characters
strtolower('MÜNCHEN');  // Does not lowercase Ü

Correct:

mb_strlen('José');         // 4 characters
mb_strtolower('MÜNCHEN'); // 'münchen'

// Prefer Laravel's Str helpers when available
Str::length('José');       // 4
Str::lower('MÜNCHEN');     // 'münchen'

Use defer() for post-response work

For lightweight work that does not need retries or crash durability, consider defer() instead of dispatching a job. During an HTTP request, the callback normally runs after Laravel sends the response, but it still runs in the same PHP process.

Queued and durable:

dispatch(new LogPageView($page));

Deferred in the current process:

defer(fn () => PageView::create(['page_id' => $page->id, 'user_id' => auth()->id()]));

Use a queued job when the work needs retries, queue controls, or durability across process failures.

Use Context for request-scoped data

The Context facade makes contextual data available across the current execution lifecycle without manually passing arguments through every layer.

// In middleware
Context::add('tenant_id', $request->header('X-Tenant-ID'));

// Later in the same execution lifecycle
$tenantId = Context::get('tenant_id');

Laravel adds visible context to log context. It also captures both visible and hidden context and restores them for queued jobs. Use Context::addHidden() for data that should propagate to queued jobs without appearing in logs. Do not place secrets in context unless you intend that propagation.

Use Concurrency::run() for parallel execution

Run independent operations concurrently through Laravel's configured concurrency driver.

use Illuminate\Support\Facades\Concurrency;

[$users, $orders] = Concurrency::run([
    fn () => User::count(),
    fn () => Order::where('status', 'pending')->count(),
]);

With a process-based driver, each closure runs in a separate PHP process that boots the application. Use concurrency when independent database queries, HTTP client calls, or computations benefit enough to offset process and serialization overhead. The sync driver runs closures sequentially and is mainly useful during testing.

Follow framework conventions

Follow Laravel conventions unless the domain or an existing schema requires an override.

Customized schema:

class Customer extends Model
{
    protected $table = 'Customer';
    protected $primaryKey = 'customer_id';

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class, 'role_customer', 'customer_id', 'role_id');
    }
}

Conventional schema:

class Customer extends Model
{
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
laravel
Publisher
Laravel (Official)
Source
laravel/boost · .ai/laravel/skill/laravel-best-practices · commit 5f8d05d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by laravel.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download laravel-best-practices.zip