ButterCMS Logo

19 Laravel best practices for developers in 2026

Published on
Updated on
18 min read
Featured Image: Laravel Best Practices for Developers in 2026
Laravel Tutorials

Laravel best practices fall into six categories: project structure, controller logic, API design, database queries, caching, and testing. This guide covers 19 actionable practices with code examples to help you write cleaner, more maintainable Laravel applications and ship with confidence.

Each practice is something you can apply to your current project today. Whether you're starting fresh or refactoring an existing codebase, these patterns keep your Laravel apps scalable, testable, and easy for your team to work with.

Laravel best practices cheat sheet

Before diving into the details, here's the full list at a glance. Use this as a quick reference, or jump to any practice that's relevant to what you're working on right now. Each one is covered in detail with code examples below.

# Practice Category Priority What it helps you avoid
1 Use the most stable release General High Security vulnerabilities, missed performance gains
2 Keep business logic in service classes Architecture High Tightly coupled controllers, duplicated logic
3 Follow controller best practices Controllers High Bloated controllers, untestable code
4 Use helper functions Code quality Medium Reinventing built-in functionality
5 Obey the single responsibility principle Architecture High Methods that do too much, hard-to-trace bugs
6 Use the Artisan CLI tool Tooling Medium Manual scaffolding, slower workflow
7 Validate in request classes Validation High Inline validation clutter, duplicated rules
8 Timeout HTTP requests Reliability Medium Indefinitely hanging requests
9 Design APIs for longevity API design High Breaking changes, inconsistent responses
10 Use mass assignments Database Medium Verbose model creation, unprotected fields
11 Chunk data for heavy tasks Performance Medium Memory exhaustion on large datasets
12 Implement a caching strategy Performance High Unnecessary database calls, slow response times
13 Keep .env out of application code Configuration High Broken config caching, missing values
14 Use Eloquent over raw SQL Database Medium Hard-to-read queries, missed ORM features
15 Structure your project for maintainability Architecture High Disorganized codebases, content in the wrong layer
16 Follow naming conventions Code quality Medium Inconsistent codebase, team confusion
17 Use standard Laravel tools Tooling Medium Unnecessary dependencies, maintenance overhead
18 Use shorter, readable syntax Code quality Low Verbose code where Laravel provides shortcuts
19 Implement the down() migration method Database Medium Failed rollbacks, broken deployments

Laravel best practices for developers in 2026

This section reviews the essential best practices when working with Laravel, covering Laravel API best practices, Laravel project structure best practices, and Laravel controller best practices. By following these guidelines, you'll be able to write clean, efficient, and maintainable code.

1. Always use the most stable release

The most recent version of Laravel, 12.x, was released on February 24, 2025. While Laravel 11.x brought major features like opt-in API and broadcast routing, Laravel 12 is more of a maintenance release. It mainly focuses on updating dependencies and improving performance. According to the official docs, no breaking changes were introduced, which should make it much easier for developers to upgrade from 11.x to 12.x.

If you haven’t updated yet, or are running an outdated version of Laravel, now’s a good time to migrate. The longer you delay framework updates, the harder they become later on.

2. Keep business logic in the service class

To ensure clean code, users must implement abstractions. While controllers typically manage business logic, there are instances where other controllers may need to reuse this logic. In such cases, you should encapsulate this logic within separate service classes. This approach helps maintain code cleanliness by avoiding redundancy and promoting reusability.

Here's an example of a bad approach where the business logic is tightly coupled to the controller and is not reusable:

namespace App\Http\Controllers;
...
class OrderController extends Controller
{
    public function placeOrder(Request $request)
    {
        $data = $request->all();
        $user = User::find($data['user_id']);
        $order = new Order();
        $order->user_id = $user->id;
        $order->total = $data['total'] + ( $data['total'] * 0.5);
        $order->status = 'pending';
        $order->save();
        // ...
    }
    . . .
}

This snippet creates an order record using the user data and the data from the request. It also calculates the total cost of the order.

To do this better, refactor the code to look like this:

namespace App\Services;
...
class OrderService
{
    public function createOrder(array $orderData)
    {
        // Encapsulated business logic
        $user = User::find($orderData['user_id']);
        $order = new Order();
        $order->user_id = $user->id;
        $order->total = $data['total'] + ( $data['total'] * 0.5);
        $order->status = 'pending';
        $order->save();
        return $order;
    }
}

Then, inject the service into the controller as demonstrated below:

...
use App\Services\OrderService;

class OrderController extends Controller
{
    private $orderService;

    public function __construct(OrderService $orderService)
    {
        $this->orderService = $orderService;
    }

    public function placeOrder(Request $request)
    {
        $orderData = $request->all();
        $order = $this->orderService->createOrder($orderData);
        // ...
    }
}

3. Follow controller best practices

Clean controllers are the foundation of a maintainable Laravel app. When your controllers stay thin and focused, your entire codebase becomes easier to test, debug, and extend.

Three principles to follow:

Use resource controllers for CRUD operations. Laravel's resource controllers give you a standardized set of RESTful routes and methods. Instead of defining each route manually, use php artisan make:controller ProductController --resource to scaffold a controller with index, create, store, show, edit, update, and destroy methods. This keeps your API endpoints predictable and your routing clean.

Inject dependencies instead of instantiating them directly. As shown in practice #2, constructor injection through Laravel's service container makes your controllers more testable and loosely coupled. When you type-hint a dependency in your constructor, Laravel resolves it automatically — no manual instantiation needed.

Keep controller methods short. If a method starts doing too much, move the logic to a service class or action class. A good rule of thumb: if a controller method exceeds 10-15 lines, it's likely handling responsibilities that belong elsewhere.

4. Use helper functions

Laravel comes with several helper functions and methods. Instead of reinventing the wheel, use the existing methods; it will keep the codebase concise and prevent repetition. A typical example is when you want to generate a random string. Instead of creating a new function that does that, you can use the Illuminate/Support/Str by doing the following:

$slug = Str::random(24);

These helpers are available anywhere within the application.

5. Obey the single responsibility principle (SRP)

A class and method should have just one responsibility. This makes software implementation easier and intercepts any unforeseen side effects that may occur due to future changes.

For instance, instead of having a method that returns the transaction data by performing several operations, as demonstrated by the snippet below:

public function getTransactionAttribute()
{
    if ($transaction && ($transaction->type == 'withdrawal') && $transaction->isVerified()) {
        return ['reference'=>$this->transaction->reference, 'status'=>'verified'];
    } else {
        return ['link'=>$this->transaction->paymentLink, 'status'=>'not verified'];
    }
}

Improve readability by spreading the actions across methods like this:

public function getTransactionAttribute(): bool
{
    return $this->isVerified() ? $this->getReference() : $this->getPaymentLink();
}

public function isVerified(): bool
{
    return $this->transaction && ($transaction->type == 'withdrawal') && $this->transaction->isVerified();
}

public function getReference(): string
{
    return ['reference'=>$this->transaction->reference, 'status'=>'verified'];
}

public function getPaymentLink(): string
{
    return ['link'=>$this->transaction->paymentLink, 'status'=>'not verified'];
}

6. Use the Artisan CLI tool

The artisan CLI tool has a lot of commands that can help scaffold a setup you want. For instance, instead of manually writing your migration files and creating the model, use the command:

php artisan make:model Branding -m

There are several other artisan commands you can use, which include:

  • The create Request command.

php artisan make:request LoginRequest
  • The optimization command, which you can use to clear cache.

php artisan optimize:clear
  • The command to run migrations

php artisan migrate
  • Also, the command to run the tests

php artisan test

Check out the Laravel documentation for even more details.

7. Carry out validation in request classes

Validation is crucial when handling user input in your Laravel application to ensure data consistency and prevent errors. Inline validation rules in controller methods can become cumbersome and repetitive. To address this, Laravel provides FormRequest classes that allow you to define validation rules in a centralized and reusable way.

Previously, you might have written validation rules directly in your Controller method like this:

public function store(Request $request)
{
    $request->validate([
        'slug' => 'required',
        'title' => 'required|unique:posts|max:255',
        ...
    ]);
}

Instead, create a dedicated FormRequest class using the artisan CLI tool:

php artisan make:request StoreRequest

In this class, define your validation rules:

class StoreRequest extends FormRequest
{
    ...
    public function rules(): array
    {
        return [
            'slug' => 'required',
            'title' => 'required|unique:posts|max:255',
            ... 
        ];
    }
}

Then, update your controller method to use the FormRequest class that you created:

public function store(StoreRequest $request)
{
    ...
}

By using FormRequest classes, you can decouple validation logic from your controller and reuse these rules across your application. This approach promotes cleaner code, reduces duplication, and makes maintenance easier.

8. Timeout HTTP requests

One of the best practices Laravel has to offer is utilizing Timeouts. By using the timeout method, you can avoid errors when sending HTTP requests from your controller. Laravel defaults to timeout requests to its server after 30 seconds. If there is a delay by the HTTP request and no error message, your app could remain stuck indefinitely.

Here is an example of how to timeout an HTTP request after 120 seconds.

public function store(StoreRequest $request)
{
    ....
    $response = Http::timeout(120)->get(...);
    ....
}

9. Design your APIs for longevity

Strong API design reduces coupling between your frontend and backend and makes your application easier to evolve over time. Three practices that pay off immediately:

Use middleware for authorization and permissions. Applying auth and permission checks at the middleware level gives you centralized control over your API endpoints. This keeps authorization logic out of your controllers and makes it easy to update permissions across multiple routes in one place.

Adopt API versioning from the start. Versioning your endpoints (e.g., /api/v1/products) lets you introduce updates and improvements without breaking existing integrations. This is especially valuable when third parties or mobile apps consume your API.

Use Eloquent API resources for response serialization. Instead of returning raw model data, use Laravel's API resources (php artisan make:resource ProductResource) to control exactly what your API returns. This gives you a consistent, maintainable response format and makes it easy to add or modify fields without touching your controller logic.

10. Take advantage of mass assignments

In Laravel, mass assignments are useful to avoid scenarios where users might unintentionally alter sensitive data, such as passwords or admin status. For instance, suppose you have a product that you want to save in the Product model. Instead of using the following code:

$product = new Product;
$product->name = $request->name;
$product->price = $request->price;
$product->save();

You can use the create static method from the model class and pass in the validated request array like so:

Product::create($request->validated());

This method handles mass assignment.

11. Chunk data for heavy data tasks

When processing a large amount of data from the database, instead of fetching the data and running a loop through the large data, like this:

$products = Product::all() /* returns thousands of data */
foreach ($products as $product) {
	...
}

Use the chunk method by specifying a fixed amount you want to process at a time and the closure for processing. Here is an example:

Product::chunk(200, function ($products) {
    foreach ($products as $product) {
        // Perform some action on the product
    }
});

12. Implement a caching strategy

A smart caching strategy keeps your app fast under load and reduces unnecessary database queries. Laravel's cache system is flexible and straightforward — the key is using it intentionally rather than as an afterthought.

Cache frequently accessed data that doesn't change often. Data like configuration settings, menu structures, or category lists are great candidates. Caching these reduces database retrieval overhead on every request.

Use cache tags for granular invalidation. Tags let you group related cached items so you can flush an entire group when the underlying data changes, without clearing your entire cache. This keeps your cached data fresh without sacrificing performance.

Leverage the remember() method. This is one of Laravel's most useful caching patterns — it checks whether data exists in cache and only queries the database if it doesn't:

$products = Cache::remember('products', 3600, function () {
    return Product::all();
});

This single method handles the check-then-store pattern that would otherwise require several lines of conditional logic.

13. Do not use environment variables (.env) directly in your code

In Laravel, use env(..) in config files to define values and config(..) in application codes to retrieve them. This ensures compatibility with the config caching system.

For example, if you have to use an external service that requires API keys, it must be secure. After you've added this key to your key store (.env file) to secure it, instead of retrieving the keys directly, just like this:

$chat_gpt_key = env("CHAT_GPT_API_KEY");

It's best to add it to your config/api.php using the following:

[
    ...
    'chat_gpt_key' => env("CHAT_GPT_API_KEY"),
]

Then, when you need to use the key within any section of your project, just fetch it using the config function provided by Laravel.

$chat_gpt_key = config('api.chat_gpt_key');

This gives you more control over the environment variable, allowing you to set a default value even when the environment variable doesn't exist in your key store (.env file). Also, using the config method has performance benefits, as Laravel caches the configurations present in the configuration files.

14. Use Eloquent instead of Query Builder and raw SQL queries

Eloquent allows you to write readable and maintainable code. It comes with useful tools like soft deletes, events, scopes, etc., and will enable you to set conditions for your database table queries to ensure your table's data stays correct. It simplifies managing relationships between models.

You don't need to write complex SQL code when you have Eloquent in Laravel. For instance, if you want to fetch a list of products and their categories, filter by availability and sort them by the most recent. Instead of using the SQL query like this:

SELECT *
FROM `products`
WHERE EXISTS (SELECT *
              FROM `categories`
              WHERE `products`.`category_id` = `categories`.`id`
              AND `categories`.`deleted_at` IS NULL)
AND `is_available` = '1'
ORDER BY `created_at` DESC

You simply use this:

Product::has('category')->isAvailable()->latest()->get();

Both code snippets are retrieving products that have a related category, are marked as available, and are sorted in descending order by creation time. While the first snippet uses raw SQL, the second uses the Eloquent method chaining to apply these conditions, making the intent of the code more explicit and easier to understand.

15. Structure your project for maintainability

A well-organized project structure makes your codebase navigable from day one and keeps it that way as your team and application grow. Three principles to follow:

Adhere to the Laravel directory structure. Laravel's default directory layout is designed for a reason — it makes finding and debugging code intuitive for any developer familiar with the framework. Resist the urge to create custom folder hierarchies unless your application genuinely outgrows the defaults.

Break large applications into modules. As your app scales, modularization keeps things manageable. Group related controllers, models, services, and routes into self-contained modules so each piece of your application can evolve independently.

Keep marketing content out of your application layer. Blog posts, landing pages, FAQs, and other marketing content don't belong in migration-managed models or hard-coded templates. A headless CMS handles this cleanly — for example, ButterCMS integrates natively with Laravel and lets your marketing team manage content independently, so your codebase stays focused on application logic.

16. Maintain Laravel naming conventions

Follow the PSR Standards as stated here, and also use the naming conventions accepted by the Laravel community, which will help in organizing your files. Using consistent naming conventions is important because inconsistencies can cause confusion, which will eventually lead to errors. Here's a tabular guideline: 

What

How

Good

Bad

Model

singular

User

Users

hasOne or belongsTo relationship

singular

articleComment

articleComments, article_comment

All other relationships

plural

articleComments

articleComment, article_comments

Table

plural

article_comments

article_comment, articleComments

Route

plural

articles/1

article/1

Pivot table

singular model names in alphabetical order

article_user

user_article, articles_users

Table column

snake_case without model name

meta_title

MetaTitle; article_meta_title

Model property

snake_case

$model->created_at

$model->createdAt

Controller

singular

ArticleController

ArticlesController

Contract (interface)

adjective or noun

AuthenticationInterface

Authenticatable, IAuthentication

Trait

adjective

Notifiable

NotificationTrait

Foreign key

singular model name with _id suffix

article_id

ArticleId, id_article, articles_id

Method

camelCase

getAll

17. Use standard Laravel tools

Laravel offers a range of tools to enhance development, including:

  • Sanctum: Simplifies authentication and authorization

  • Socialite: Streamlines social media authentication

  • Jetstream: Accelerates application scaffolding and provides pre-built UI components

  • Mix: Optimizes asset compilation and management

These tools standardize your code, make it maintainable, and are regularly updated with community support.

18. Use shorter and more readable syntax in your Laravel code

Using shorter syntax makes your Laravel code readable and consistent and reduces cognitive load. For example, if you want to get the particular session data from the request session, instead of using the following:

$request->session()->get('order') 

or

Session::get('order')

You can simply use this:

session('order')

19. Implement the down() migration method

Most developers often overlook implementing the down() method in their migration file. This neglect can have significant consequences, particularly in successfully executing rollbacks. Therefore, it is a highly recommended Laravel best practice to always implement the down() method for every up() method in your migration file.

For instance, if you have an orders table migration file that creates a new column, fee:

return new class extends Migration
{

    public function up()
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->unsignedInteger('fee')->default(0);
        });
    }
...
};

You’ll need to implement the down() method, which negates the creation of the column fee.

return new class extends Migration
{

    public function up()
    {
       . . . 
    }

    public function down()
    {
        Schema::table('orders', function (Blueprint $table) {
            $table->dropColumn('fee');
        });
    }
}

The future of Laravel

Laravel, one of the most popular PHP frameworks and already on its 12th version, will likely continue advancing and growing for the foreseeable future. As we look beyond version 12, here are some exciting possibilities that might shape the future of Laravel:

  • Deeper embrace of cloud-native technologies such as serverless architecture
  • Laravel might openly embrace GraphQL, allowing developers to build their GraphQL APIs
  • Major improvements to existing tools in the Laravel ecosystem, as well as the introduction of new tools to enhance developer experience

A laravel themed astronaut riding a rocket to space.

Final thoughts

Following the Laravel best practices outlined in this article gives you a cleaner, more scalable codebase — and a faster development workflow for your entire team. Whether you're structuring your project, designing APIs, or optimizing performance with caching, each practice builds on the others to create applications that are easier to maintain and extend.

If you're looking to add CMS-powered content to your Laravel app without fighting your framework, ButterCMS integrates natively with Laravel and keeps your codebase just as clean as these best practices recommend. Your marketing team gets full control over blog posts, landing pages, and SEO content while your application layer stays focused on what it does best. Start a free trial to see how it fits, or request a demo and we'll walk you through it. You can also check out our guide on building a Laravel blog with ButterCMS to jump straight into a hands-on example.

FAQ

What is Laravel?

Laravel is an open-source PHP framework built on the Model-View-Controller (MVC) pattern. It's designed to make common development tasks like routing, authentication, caching, and database management simpler and more expressive, so you can focus on building your application rather than re-solving infrastructure problems.

What problems does Laravel solve?

Laravel addresses many of the pain points PHP developers encounter: it provides built-in authentication and authorization, pre-configured error handling, a task scheduling system, an expressive CLI (Artisan) for scaffolding and automation, out-of-the-box testing tools like Pest and PHPUnit, and a dedicated notification and event system. Its large community also means vulnerabilities are identified and patched quickly.

What are the most important Laravel best practices?

The highest-impact practices are keeping business logic in service classes (not controllers), validating in FormRequest classes, using Eloquent over raw SQL, following Laravel's naming conventions, and implementing a caching strategy for frequently accessed data. These five practices alone eliminate the most common sources of technical debt in Laravel applications.

What is the Laravel project structure best practice?

Stick to Laravel's default directory structure unless your application genuinely outgrows it. As your app scales, break it into self-contained modules that group related controllers, models, services, and routes. Keep marketing content (blog posts, landing pages, FAQs) out of your application layer entirely. A headless CMS like ButterCMS handles this cleanly through API integration.

How do I write clean controllers in Laravel?

Keep controllers thin. Use resource controllers for CRUD operations, inject dependencies through the constructor rather than instantiating them directly, and move any business logic that exceeds 10-15 lines into a service or action class. Your controllers should coordinate, not compute.

What is the difference between Laravel API resources and regular responses?

API resources (php artisan make:resource) give you a dedicated transformation layer between your Eloquent models and your JSON responses. Instead of returning raw model data, which exposes your database structure and can change unpredictably, API resources let you define exactly which fields to include, how to format them, and how to handle relationships. This makes your API responses consistent, versioned, and maintainable.

What are Laravel testing best practices?

Structure your tests using the Arrange-Act-Assert (AAA) pattern for readability. Use the RefreshDatabase trait so each test starts with a clean slate. Write integration tests that verify interactions between different parts of your application—including database operations, API requests, and external service calls. Mock external services like payment gateways so your tests run reliably without depending on third-party availability.

Author

Maab is an experienced software engineer who specializes in explaining technical topics to a wider audience.