Hire the Top 3% of Freelance Laravel Developers

Toptal is a marketplace for top Laravel developers, engineers, programmers, coders, architects, and consultants. Top companies and startups choose Toptal Laravel freelancers for their mission-critical software projects.

No-Risk Trial, Pay Only If Satisfied.

Hire Freelance Laravel Developers and Engineers

Bryce Ott

Freelance Laravel Developer

United StatesToptal Member Since June 23, 2014

With almost 20 years working as an engineer, architect, director, vice president, and CTO, Bryce brings a deep understanding of enterprise software, management, and technical strategy to any project. His specialties include Amazon Web Services, real-time systems, business intelligence, big data, enterprise web apps, scalability, education, and open-source software.

Show More

Philip Bennett

Freelance Laravel Developer

GermanyToptal Member Since April 30, 2018

Phil is an experienced web developer with 17 years of experience in PHP and WordPress. Focusing on high-quality hyper-performance deliveries, Phil is a pragmatic technologist who chooses the right technology and applies it effectively to deliver projects on time and on budget. Phil has a balanced full-stack knowledge and is currently delivering WordPress based site for large multinational companies.

Show More

Brian Danchilla

Freelance Laravel Developer

CanadaToptal Member Since December 17, 2013

Brian is a full-stack software developer with over 15 years of experience. Over the past decade, he has run his own consulting business, which provides both cutting edge solutions and legacy system maintenance. Brian strives to write clean, modern, reliable code. Always honing his craft, Brian keeps abreast with the rapidly changing industry.

Show More

Jonathan Serle

Freelance Laravel Developer

IsraelToptal Member Since July 24, 2014

Jonathan is a highly motivated developer and architect with a unique edge: his communication skills and ability to understand software projects from the top-down. His experience has taken him through a wide range of development environments, from the startup to the Fortune 500.

Show More

Aleksei Fedorenko

Freelance Laravel Developer

RussiaToptal Member Since September 25, 2017

Over the past 6+ years, Aleksei has worked for an online service company, a digital agency, and as a remote freelancer. He builds clean and reliable user interfacing with HTML, CSS, and modern JavaScript frameworks. Aleksei is experienced in both continuous maintenance of large projects and short-term, rapid development using the vast scope of front-end technologies.

Show More

Nick Kuhn

Freelance Laravel Developer

United StatesToptal Member Since March 10, 2017

Nick has over a decade of Drupal experience and has built some of the largest and most recognized Drupal websites such as and With a very wide skillset, he fits in perfectly with any sized team, and in any role. He has worked with Acquia and developed several of the best practices the community uses today. With over 50 projects under his belt, he has the real-life experience needed to make applications successful.

Show More

Alain Schlesser

Freelance Laravel Developer

GermanyToptal Member Since October 7, 2016

Alain is a freelance software engineer, WordPress consultant, and Google developers expert (GDE) for web technologies, specializing in enterprise-level WordPress development, planning scalable architectures, and refactoring legacy code to make it fit for the next decade. His more than 25 years of development work span numerous languages and platforms with certifications for Oracle SQL and SharePoint, among others. Alain currently focuses on modern object-oriented PHP and domain-driven design.

Show More

Sign up now to see more profiles.

Start Hiring

Proven with a 98% success rate. Experience it today with a no-risk trial.

Pair freelance management consultants with a full team of SMEs for end-to-end value creation.

Start Hiring
Frequently paired together

Get Additional Expertise

Our clients frequently pair these additional services with our freelance Laravel Developers.

A Hiring Guide

Guide to Hiring a Great Laravel Developer

Laravel is powerful and eloquent; novice developers can often get quite far with it. But in the wrong hands, even the best of tools can be used to make unscalable, unmaintainable products. Hiring real experts means having a handle on Laravel basics and best practices, as covered in this comprehensive hiring guide.

Read Hiring Guide
Toptal in the press

... allows corporations to quickly assemble teams that have the right skills for specific projects.

Despite accelerating demand for coders, Toptal prides itself on almost Ivy League-level vetting.

Our clients
Building a cross-platform app to be used worldwide
Thierry Jakicevic
Building a cross-platform app to be used worldwide
Creating an app for the game
Conor Kenney
Creating an app for the game
Leading a digital transformation
Elmar Platzer
Leading a digital transformation
What our clients think
Clients Rate Toptal Laravel Developers4.2 / 5.0on average across 384 reviews as of Aug 2, 2023

Tripcents wouldn't exist without Toptal. Toptal Projects enabled us to rapidly develop our foundation with a product manager, lead developer, and senior designer. In just over 60 days we went from concept to Alpha. The speed, knowledge, expertise, and flexibility is second to none. The Toptal team were as part of tripcents as any in-house team member of tripcents. They contributed and took ownership of the development just like everyone else. We will continue to use Toptal. As a start up, they are our secret weapon.

Brantley Pace, CEO & Co-Founder


I am more than pleased with our experience with Toptal. The professional I got to work with was on the phone with me within a couple of hours. I knew after discussing my project with him that he was the candidate I wanted. I hired him immediately and he wasted no time in getting to my project, even going the extra mile by adding some great design elements that enhanced our overall look.

Paul Fenley, Director

K Dunn & Associates

The developers I was paired with were incredible -- smart, driven, and responsive. It used to be hard to find quality engineers and consultants. Now it isn't.

Ryan Rockefeller, CEO


Toptal understood our project needs immediately. We were matched with an exceptional freelancer from Argentina who, from Day 1, immersed himself in our industry, blended seamlessly with our team, understood our vision, and produced top-notch results. Toptal makes connecting with superior developers and programmers very easy.

Jason Kulik, Co-Founder


As a small company with limited resources we can't afford to make expensive mistakes. Toptal provided us with an experienced programmer who was able to hit the ground running and begin contributing immediately. It has been a great experience and one we'd repeat again in a heartbeat.

Stuart Pocknee , Principal

Site Specific Software Solutions

We used Toptal to hire a developer with extensive Amazon Web Services experience. We interviewed four candidates, one of which turned out to be a great fit for our requirements. The process was quick and effective.

Abner Guzmán Rivera, CTO and Chief Scientist

Photo Kharma

Sergio was an awesome developer to work with. Top notch, responsive, and got the work done efficiently.

Dennis Baldwin, Chief Technologist and Co-Founder


Working with Marcin is a joy. He is competent, professional, flexible, and extremely quick to understand what is required and how to implement it.

André Fischer, CTO


We needed a expert engineer who could start on our project immediately. Simanas exceeded our expectations with his work. Not having to interview and chase down an expert developer was an excellent time-saver and made everyone feel more comfortable with our choice to switch platforms to utilize a more robust language. Toptal made the process easy and convenient. Toptal is now the first place we look for expert-level help.

Derek Minor, Senior VP of Web Development

Networld Media Group

Toptal's developers and architects have been both very professional and easy to work with. The solution they produced was fairly priced and top quality, reducing our time to launch. Thanks again, Toptal.

Jeremy Wessels, CEO


We had a great experience with Toptal. They paired us with the perfect developer for our application and made the process very easy. It was also easy to extend beyond the initial time frame, and we were able to keep the same contractor throughout our project. We definitely recommend Toptal for finding high quality talent quickly and seamlessly.

Ryan Morrissey, CTO

Applied Business Technologies, LLC

I'm incredibly impressed with Toptal. Our developer communicates with me every day, and is a very powerful coder. He's a true professional and his work is just excellent. 5 stars for Toptal.

Pietro Casoar, CEO

Ronin Play Pty Ltd

Working with Toptal has been a great experience. Prior to using them, I had spent quite some time interviewing other freelancers and wasn't finding what I needed. After engaging with Toptal, they matched me up with the perfect developer in a matter of days. The developer I'm working with not only delivers quality code, but he also makes suggestions on things that I hadn't thought of. It's clear to me that Amaury knows what he is doing. Highly recommended!

George Cheng, CEO

Bulavard, Inc.

As a Toptal qualified front-end developer, I also run my own consulting practice. When clients come to me for help filling key roles on their team, Toptal is the only place I feel comfortable recommending. Toptal's entire candidate pool is the best of the best. Toptal is the best value for money I've found in nearly half a decade of professional online work.

Ethan Brooks, CTO

Langlotz Patent & Trademark Works, Inc.

In Higgle's early days, we needed the best-in-class developers, at affordable rates, in a timely fashion. Toptal delivered!

Lara Aldag, CEO


Toptal makes finding a candidate extremely easy and gives you peace-of-mind that they have the skills to deliver. I would definitely recommend their services to anyone looking for highly-skilled developers.

Michael Gluckman, Data Manager


Toptal’s ability to rapidly match our project with the best developers was just superb. The developers have become part of our team, and I’m amazed at the level of professional commitment each of them has demonstrated. For those looking to work remotely with the best engineers, look no further than Toptal.

Laurent Alis, Founder


Toptal makes finding qualified engineers a breeze. We needed an experienced ASP.NET MVC architect to guide the development of our start-up app, and Toptal had three great candidates for us in less than a week. After making our selection, the engineer was online immediately and hit the ground running. It was so much faster and easier than having to discover and vet candidates ourselves.

Jeff Kelly, Co-Founder

Concerted Solutions

We needed some short-term work in Scala, and Toptal found us a great developer within 24 hours. This simply would not have been possible via any other platform.

Franco Arda, Co-Founder

Toptal offers a no-compromise solution to businesses undergoing rapid development and scale. Every engineer we've contracted through Toptal has quickly integrated into our team and held their work to the highest standard of quality while maintaining blazing development speed.

Greg Kimball, Co-Founder

How to Hire Laravel Developers through Toptal


Talk to One of Our Industry Experts

A Toptal director of engineering will work with you to understand your goals, technical needs, and team dynamics.

Work With Hand-Selected Talent

Within days, we'll introduce you to the right Laravel developer for your project. Average time to match is under 24 hours.

The Right Fit, Guaranteed

Work with your new Laravel developer for a trial period (pay only if satisfied), ensuring they're the right fit before starting the engagement.


  • How are Toptal Laravel developers different?

    At Toptal, we thoroughly screen our Laravel developers to ensure we only match you with talent of the highest caliber. Of the more than 200,000 people who apply to join the Toptal network each year, fewer than 3% make the cut. You’ll work with engineering experts (never generalized recruiters or HR reps) to understand your goals, technical needs, and team dynamics. The end result: expert vetted talent from our network, custom matched to fit your business needs.

  • Can I hire Laravel developers in less than 48 hours through Toptal?

    Depending on availability and how fast you can progress, you could start working with a Laravel developer within 48 hours of signing up.

  • What is the no-risk trial period for Toptal Laravel developers?

    We make sure that each engagement between you and your Laravel developer begins with a trial period of up to two weeks. This means that you have time to confirm the engagement will be successful. If you’re completely satisfied with the results, we’ll bill you for the time and continue the engagement for as long as you’d like. If you’re not completely satisfied, you won’t be billed. From there, we can either part ways, or we can provide you with another expert who may be a better fit and with whom we will begin a second, no-risk trial.


How to Hire a Great Laravel Developer

Since its initial release in 2011, Laravel’s usage and developers’ interest in it grew exponentially, establishing it as the most popular open-source PHP framework currently available. Contributing factors to this were its extremely rich feature set, excellent documentation, code elegancy, and on top of it all, incredible simplicity.

While from the developer’s perspective, all of this sounds great—and it really is—Laravel development’s flat learning curve means employers need to take extra steps to ensure they’re hiring a part-time or full-time developer capable of delivering quality, scalable code. This article will focus on introducing Laravel’s extensive feature set as well as some core programming concepts which will equip interviewers to construct effective questions and recognize developers with adequate experience and expertise for their project requirements.


Before we dive into Laravel specifics, we’re going to introduce a few programming and web application concepts. It’s important interviewers are familiar with these concepts because we’re going to be referring to them later, and understanding them will also enable you to gauge a potential employee’s programming knowledge.

Model-view-controller (MVC)

MVC is a software design pattern. Simply put, it dictates that all application code should be separated into three layers:

  • The data layer (model) stores and/or prepares data based on the controller’s instructions.
  • The presentation layer (view) displays to the end user the data received from the controller.
  • The “glue” layer (controller) handles incoming requests and instructs the data layer on what to do with the request data. Based on the request, it gets the data layer to prepare new data, which is then sent to the presentation layer for rendering.

The Laravel framework is based around the MVC pattern, but it does not enforce developers’ use of MVC. This means that a developer not being familiar with MVC and not honoring best practices can result in poorly written code, regardless of the framework.

Let’s look at examples of poorly written and well-written code:


    $pdo = new PDO('mysql:host=localhost;dbname=laraveldb', 'root', 'pass');
    $query = $pdo->query('select * from articles');
    $results = $query->fetchAll(PDO::FETCH_ASSOC);

    <?php foreach ($results as $articleData): ?>

        <h2><?php echo $articleData['title']; ?></h2>
        <p><?php echo $articleData['content']; ?></p>

    <?php endforeach; ?>


In this example, we interact with the database directly in our presentation layer. We also don’t have a model for the articles table, or a controller which would send data to the view. Let’s rewrite this in Laravel.

The Article model:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model

Our controller, which will pass all of the Articles to the view:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Article;

class ArticleController extends Controller
    public function index()
        return view('articlesList', ['articles' => Article::all()]);

Now, the $articles variable will be available in our articlesList view:


    @foreach ($articles as $article)

        <h2>{{ $article->title }}</h2>
        <p>{{ $article->content }}</p>



Each part of the code is now separated into its respective layer, making it clean and maintainable.

Note: This example uses syntax like Article::all() and @foreach that will be explained later in the guide.

Dependency Injection

Dependency injection is a software design concept which states that a client (object) should only know which dependencies (e.g., services) it needs to perform certain functions, but should not know who supplies these dependencies or how they’re constructed. This allows for loose coupling, which makes the code more flexible and less prone to errors. We’ll talk more about this later.

Let’s take a look at an example:

class Article
    protected $contentFormatter;
    protected $content;

    public function __construct($content)
        $this->contentFormatter = new ContentFormatter;
        $this->content = $content;

    public function getFormattedContent()
        return $this->contentFormatter->format($this->content);

$article = new Article('Lorem Ipsum Content');
$formattedContent = $article->getFormattedContent();

We can see how the Article class is responsible for instantiating the ContentFormatter, when all it should really care about is providing the formatted content. Let’s implement dependency injection:

class Article
    protected $contentFormatter;
    protected $content;

    public function __construct($contentFormatter, $content)
        $this->contentFormatter = $contentFormatter;
        $this->content = $content;

    public function getFormattedContent()
        return $this->contentFormatter->format($this->content);

$contentFormatter = new ContentFormatter;
$article = new Article($contentFormatter, 'Lorem Ipsum Content');
$formattedContent = $article->getFormattedContent();

This might not seem like a big improvement at the moment, but it will become much more apparent how this technique can solve architectural problems and make the code easier to maintain once we’ve covered the Service Container section of this guide.

Object-relational Mapping (ORM)

ORM is a technique for converting data from relational database tables into objects, and vice-versa. Once constructed, these objects can then manipulate data via the API provided by the ORM implementation they’re using, and save it into the database. This greatly simplifies interacting with the database through code, and in most cases, does not require writing any SQL at all.

Here are some examples of how this works:

Article::where('is_active', 1)->get();
// select * from articles where is_active = 1

    'title' => 'Some Article Title', 
    'content' => 'Lorem ipsum content'
// insert into articles (title, content) values 
// ("Some Article Title", "Lorem ipsum content")

// delete from users where email is null

Laravel Core Architecture

By now, using everything we’ve talked about so far, managers should be able to craft interview questions that will give insight into a developer’s understanding of some core programming concepts, and help weed out those who lack the experience to build the company’s next amazing project. But what about Laravel’s specific architecture and features?

Service Container

The service container is the main component of Laravel’s dependency injection implementation. It provides several ways of registering different types of bindings. Perhaps the most commonly used container’s feature is binding an interface to a given implementation.


This code instructs the container to inject StripePaymentGateway when PaymentGateway service is required by a class:

class PaymentController
    protected $paymentGateway;

    public function __construct(PaymentGateway $paymentGateway)
        $this->paymentGateway = $paymentGateway;

Provided our PaymentController is resolved from the service container, its $paymentGateway property will be an instance of StripePaymentGateway. Remember the loose coupling we mentioned earlier? This is it. We’ve decoupled the client from the PaymentGateway implementation, which means, if at some point we decide to replace Stripe with some other payment gateway, we can just bind PaymentGateway interface to our new SomeOtherPaymentGateway implementation, and everything else will work without changing any of the code.

Service Providers

Service providers serve the purpose of bootstrapping a Laravel application. This includes registering different bindings, event listeners, or routes, as well as configuring things like macros. All service providers must be registered within the config/app.php config file.

Service providers include two methods: register and boot. The register method should only be used to bind things into the service container. The boot method is called after all the service providers have been registered, meaning its code has access to all the dependencies registered in any service provider. This method also supports type-hinting dependencies:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\SomeService;

class PaymentGatewayServiceProvider extends ServiceProvider
    public function register()

    public function boot(SomeService $someService)

Laravel bootstraps all of its components through service providers, and by default ships with an empty provider (AppServiceProvider) so developers can use it for any additional custom bootstrapping. In larger applications, additional bootstrapping can be split between any number of different custom service providers.

Request Lifecycle

All HTTP requests are handled by the HTTP kernel. Internally, the HTTP kernel runs all required bootstrappers before the request is actually handled, which includes loading all of the service providers. Next, the request is passed to the router. The router runs all the applicable middleware—which we’ll discuss later—and then dispatches the request to the specified controller.


Understanding Laravel’s architecture is one of the keys to writing clean, flexible, and maintainable code. The developer should know where certain parts of the code belong and how different components of the framework work together. Their ability to answer questions on these topics should give interviewers confidence.

A flowchart showing a request going through the kernel, router, and controller. The kernel loads service providers (auth, cache, cookie, route), the router runs route-specific middleware and matches the request to a controller method and dispatches it, and the controller prepares the response data based on the request and returns the response to the user.

Laravel Fundamentals

With Laravel’s core architecture clear, let’s move on to Laravel’s extensive feature set, with which every seasoned developer should be well familiar. Each one of these modules is very likely to be used in the majority of Laravel-based applications, so interviewers should make sure candidates understand all of the concepts in this section.


Routing is a way of directing requests to controller methods based on the URL of a request. While Laravel’s router is a very powerful tool, we won’t go into details about all of its features. Instead, we’ll look at its basic functionalities and the things you should pay attention to while reviewing a candidate’s code.

Here’s an example of a simple route definition:


That route will direct all /posts GET requests to the index method of PostController.

By default, Laravel comes with web.php and api.php routes, located in the /routes folder, and, as we mentioned before, a sensible set of middleware assigned to these routes via the RouteServiceProvider.

The registered routes support all HTTP verbs:


It’s also possible to match multiple, or all verbs:

Route::match(['get', 'post'], ...);


Routes also accept a closure instead of a controller action:

Route::get('post', function() {
    $posts = Post::all();

    return view('postList')->with(['posts' => $posts]);

…however, this is considered bad practice in most cases and should not be used. It clutters the code and prevents the routes from being cached. Developers who are fairly familiar with Laravel will know this and avoid it.

When defining routes, you’ll often need access to parts of the route URI called parameters. Parameters are segments encased within {}, can be optional ({param?}), and are automatically sent to the controller method:


// ...

// UserPostController's show method
public function show($userId, $postId = null) { ... }

Another feature of the router is named routes:


This makes it possible to easily generate URLs for routes in our views:

<a href="{{ route('dashboard') }}">Dashboard</a>

Or, for example, redirect the user after a successful login:

return redirect()->route('dashboard');

While the router offers many more features, this sums up the basic ones. Proficient use of these features should come effortlessly for an experienced Laravel developer.


Middleware is a type of filtering layer that HTTP requests must pass through before entering the application. For example, authentication middleware can reject the request if the user is not logged in, role middleware can redirect the user based on their user role, etc.

By default, Laravel ships with a web middleware group, which contains several useful middleware like session handling and cookie handling. It’s applied to all web requests by the RouteServiceProvider.

Let’s see what a middleware class may look like:

namespace App\Http\Middleware;

use Closure;

class UserIsAdmin
    public function handle($request, Closure $next)
        if (! $request->user()->isAdmin()) {

        return $next($request);

When applied to a route, this middleware will make sure non-admin users are denied access to that route (we’ll talk more about routing in a bit).

Middleware can be applied globally for every request, and per route as a single piece of middleware or a group of middleware. All middleware should be registered in the /app/Http/Kernel.php class.

To run a piece of middleware for every HTTP request, add it to the list in the $middleware property of the HTTP kernel class.

To assign middleware to a particular route, coders need to first add them to the list in the $routeMiddleware property:

// App\Http\Kernel Class

protected $routeMiddleware = [

    // Other middleware here...

    'admin' => App\Http\Middleware\UserIsAdmin::class,

Now, the 'admin' middleware can be assigned to a route:


When assigning multiple middleware to a route, developers may want to group it first under a single key in the $middlewareGroups property:

protected $middlewareGroups = [
    'activeAdmin' => [

And then assign it to a route using that key:


Let’s look at our AdminController@index method before applying the middleware:

// AdminController

public function index(Request $request)
    if (! $request->user()->isAdmin()) {

    return view('user.profile', ['user' => User::findOrFail($id)]);

Imagine if we had several methods in the AdminController which only admin users should have access to. We’d have to check for that condition in every single method, which would unnecessarily clutter the code and make it harder to maintain. After applying the admin middleware to our administration routes, we can clean up the code by getting rid of the condition check in the controller methods:

public function index(Request $request)
    return view('user.profile', ['user' => User::findOrFail($id)]);

When looking through the candidate’s code examples, this is a detail managers would do well to pay attention to.


Serving as glue between the presentation and data layers, controllers are one of the key components of the MVC pattern. Pointing routes to controller methods allows you to easily organize request-handling logic within controller classes, which significantly improves code readability.

Controller methods can return several types of responses. Let’s list the most common ones:

// View:
return view('homepage')->with(['someVariable' => 'some value']);

// Response object:
return response($content)->header('Some-Header', 'Header-Value')->cookie('cookieName', 'cookieValue', $minutes);

// JSON:
return response()->json(['data' => $data]);

// File download:
return response()->download($filePath)->deleteFileAfterSend();

// Redirect
return redirect()->route('dashboard');

And a few other response types that may not be used as often:

// String
return 'Success';

// Array
return [1, 2, 3];

// Redirect to a controller action:
return redirect()->action('DashboardController@index');

// Redirect to external links:
return redirect()->away('');

Earlier, we talked about assigning middleware to routes. There’s also another way of assigning middleware: directly in the controller’s constructor. Specifying middleware in this way also supports attaching the middleware only to specific methods, or omitting it for specific methods. Let’s look at an example:

public function __construct()

One more important thing to address when it comes to controllers is dependency injection. When constructing a controller object, Laravel’s service container will inject type-hinted dependencies both in its constructor:

public function __construct(GuzzleHttp\Client $client)
    $this->client = $client;

…and in the called methods, where the most common injected dependency will be the Request instance:

public function update(Illuminate\Http\Request $request)
    // ...

This covers the essential features of Laravel’s controllers. Understanding how controllers work and being able to use their features efficiently is a must for every dedicated Laravel developer.


Sessions provide a way to retain information across multiple HTTP requests: user authentication state, form data that didn’t completely validate, etc. Laravel supports multiple session drivers:

  • file
  • cookie
  • database
  • memcached
  • redis

The default driver is file, because that will work out of the box on basically any setup, and is adequate for most applications. But memcached and redis are preferred because they’re in-memory drivers, and therefore extremely performant.

There are two ways to use the session feature provided by Laravel: Firstly, if we have access to the Request instance, we can call the session methods in the following way:


Alternatively, we can use the session() helper, which will work anywhere:


Let’s look at the most commonly used methods for manipulating session data:

// Get value for 'key', or default value if it doesn't exist:
session('key', 'default');

// Get all data in the session

// Store value for 'key':
$request->session()->put('key', 'value');
session()->put('key', 'value');
session(['key' => 'value']);

// Delete key

// Delete all session data:

Since maintaining state across multiple requests is one of the key features of web development, it’s important the developer is familiar with sessions and differences between supported session drivers.


Validation is a way to check incoming request data against a set of rules. Laravel provides a simple, yet powerful, API to perform validation using numerous supported types of rules. As with many other Laravel topics, this one is extensive, so we’ll cover the basics that should be part of every good Laravel programmer’s toolset.

There are three different ways of using the validator. Firstly, if we have access to the Request instance, we can call the validate() method on it:

public function update(Request $request)
    $validatedData = $request->validate([
        'name' => 'required|string|max:150',
        'email' => 'required|email|max:150'

    // Use the validated data...

Secondly, we can type-hint a form request on a controller method. Form requests are custom request classes where developers can put validation logic, and they significantly reduce the clutter in controllers. This is the preferred method of performing validation in most cases:

public function update(UpdateUser $request)
    // The request is automatically validated against the rules
    // defined in the rules() method in the UpdateUser class

    // Fetch the validated data
    $validated = $request->validated();

This is what the rules() method would look like:

public function rules(Request $request)
    return [
       'name' => 'required|string|max:150',
       'email' => 'required|email|max:150'

Lastly, if the previous methods don’t give enough control over validation or how the validation error responses are generated by Laravel, we can manually create a Validator instance:

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class UserController extends Controller
    public function update(Request $request)
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:150',
            'email' => 'required|email|max:150'

        if ($validator->fails()) {
            return redirect()

        // Update user

The official list of validation rules can be good interview question fodder.

With that, we’ve covered the most important features of Laravel’s validator, and hopefully given you insight into what to look for in the candidate’s code samples.

Blade Templating

Blade is Laravel’s powerful, easy-to-use templating engine. While we could write an entire article just about Blade, we’re only going to go through the essential features every Laravel web developer should be familiar with.

Perhaps Blade’s most-used feature is its ability to extend layouts, use sections, and include sub-views. Let’s take a look at an example:

<!-- Main Layout -->




Here, we have a base layout which is located in /resources/views/layout.blade.php. It includes a header subview, /resources/views/header.blade.php, and it displays content from the content section of the view which extends the layout. Here’s how we would write the /resources/page.blade.php view containing that section:



    <p>Lorem ipsum dolor sit amet.</p>


Our header subview could look like this:

    <h1>Lorem Page</h1>

Now, we just need to return the page.blade.php view from our controller method:

public function show()
    return view('page');

…and the rendered HTML will look like this:


            <h1>Lorem Page</h1>

        <p>Lorem ipsum dolor sit amet.</p>


However, rather than just displaying static content, often, we’ll need to send data from controller methods to the Blade views where they’ll need to be displayed. This can be done very easily:

public function show()
    return view('page')->with(['pageTitle' => 'Some Page']);

The $pageTitle variable will now be accessible in our views. Let’s display it in the header:

    <h1>{{ $pageTitle }}</h1>

Sometimes, we’ll also have collections of data which we’ll need to iterate over and display in the view. For example, let’s pull Posts from the database:

public function show()
    $posts = Post::all();

    return view('postList')->with(['posts' => $posts]);

In resources/views/postList.blade.php, Blade’s @foreach and @if statements make it a breeze to display only the active ones:



    @foreach($posts as $post)

        @if ($post->is_active)

            <h2>{{ $post->title }}</h2>

            {{ $post->content }}




While there’s a lot more to the Blade templating engine, these are the fundamentals needed to write clean, scalable front-end code. In few cases, if ever, will Blade require any workaround solutions for a problem, so this is something to consider when evaluating the candidate’s code.


Laravel comes with a complete and powerful, yet simple, authentication system implementation. By default, it uses the App\User model—specifically its email and password properties—to authenticate users.

Interacting with the authentication system is done in one of two ways:

  1. Using the Illuminate\Support\Facades\Auth facade
  2. Using the auth() helper function

Both support the same methods, so using one or the other is just developer’s preference.

Let’s look at the most commonly used methods when working with authentication:

// Attempt to log in a user using credentials supplied in the request
if (auth()->attempt(['email' => $request->input('email'), 'password' => $request->input('password')])) {
    // User is authenticated

// Log in an existing user instance

// Log a user in using their ID

// Retrieve authenticated user
$user = auth()->user();

// Log the user out

// Determine if the current user is logged in...
if (auth()->check()) {
    // User is logged in

// ...or not
if (auth()->guest()) {
    // User is not logged in

This is a brief overview of how Laravel’s authentication system works. However, for most applications, this is really everything that’s needed to take advantage of its features. That, in turn, speeds up the development process by doing all the heavy lifting when it comes to everything authentication-related. Having a good grasp of the authentication system is one of the things that makes a great Laravel expert.


Authorization is a way of allowing or preventing users to do certain actions—for example, creating a page or updating their username. There are two different approaches: Gates and Policies. Gates are closure-based and are mostly used for actions which are not related to any particular model, while policies are class-based with each policy being specific for a single model.

Let’s look at a couple of Gates, which will usually be defined in the AuthServiceProvider:

public function boot()

    Gate::define('update-username', function ($user) {
        return $user->role == 'admin';

    Gate::define('delete-page', function ($user, $page) {
        return $user->role == 'admin' && $page->canBeDeleted();

Gates always receive a user instance as the first argument, and can receive any number of additional arguments. To use Gates, we have two methods at our disposal—allows and denies:

if (Gate::allows('update-username')) {
    // Current user can update their username...

if (Gate::denies('delete-page', $page)) {
    // Current user cannot delete the page...

Unlike gates, each policy’s focus is a single model. For example, if you have an application which has categorized items, you’re likely going to have an Item model and an ItemPolicy that authorizes actions for that model.

Policies are registered in the policies property of the AuthServiceProvider, where all models are mapped to their corresponding policies. The easiest way to create a policy is via the artisan make:policy command:

php artisan make:policy ItemPolicy

The policy should contain a corresponding method for each model action that needs to be authorized, like update or delete, and should return true if the action is authorized, or false if it’s not. Each method will receive the user instance as its first parameter, and optionally an instance of the model for which the action is being authorized:

namespace App\Policies;

use App\Item;
use App\User;

class ItemPolicy
    // Every user can create an Item
    public function create(User $user)
        return true;

    // Only item's owner can update the Item
    public function update(User $user, Item $item)
        return $item->user_id == $user->id;

    // Only administrator can delete an item
    public function delete(User $user, Item $item)
        return $user->role == 'admin';

There are several ways we can use policies. The most common ones are calling the can and cant methods on the user instance:

if ($user->can('create', App\Item::class)) {
    // ...

if ($user->cant('update', $item)) {
    // ...

…and calling the authorize method within a controller method on the controller instance:

public function delete(Request $request, $itemId)
    $item = Item::find($itemId);

    $this->authorize('delete', $item);

    // User can delete the item

Gates and policies offer a clean and simple way to extract and group an app’s authorization logic. A proficient Laravel developer will use these methods to keep their code clean, readable, and scalable.


The Collection class is a powerful wrapper for PHP arrays. It provides numerous convenient methods for working with arrays, such as map, filter, and contains.

Creating a collection is very simple. We just call the collect() helper function and pass an array as an argument:

 $collection = collect(['some', 'values', 'here']);

Now, let’s take a look at some examples of how collections can make a developer’s life easier and the code cleaner:

collect([20, 13, 18, 22])->sort()->values()->toArray();
// [13, 18, 20, 22]

    ['name' => 'John', 'age' => 20],
    ['name' => 'Jane', 'age' => 30],
    ['name' => 'Mark', 'age' => 35],
    ['name' => 'Buzz', 'age' => 18]
])->filter(function($item) {
    return $item['age'] >= 30;
// [['name' => 'Jane', 'age' => 30], ['name' => 'Mark', 'age' => 35]]

    ['page' => 'Home'],
    ['page' => 'About Us']
])->map(function($item) {
    $item['slug'] = Str::slug($item['page']);
    return $item;
// [['page' => 'Home', 'slug' => 'home'], ['page' => 'About Us', 'slug' => 'about-us']]

This is just a tiny fraction of what the Collection class is capable of. In a proficient developer’s hands, collections will all but render PHP’s native functions for working with arrays obsolete—speeding up development and increasing code readability. Good Laravel developers will use collections wherever possible; anything less should be considered a red flag.


Artisan is a command-line interface (CLI) that ships with Laravel. It provides numerous convenient commands for automating tedious, everyday tasks while writing an application, like generating controllers, models, and jobs; caching config, routes, and views; clearing the cache; creating migrations; and many more. (Running the php artisan list command gives a full list of commands.)

While the built-in commands are sufficient in most cases, sometimes, a developer will want to be able to execute some arbitrary, custom code via an Artisan command. They can do so by creating a custom command, which will be placed in the /app/Console/Commands folder:

php artisan make:command PruneUsers

Custom commands consist of $signature and $description properties, and the handle() method. $signature is used to run the command—for example, php artisan prune:users—and it will be displayed alongside the $description when artisan list is called. When running the command, the handle() method is called. This is where developers place the code to be run, and where they type-hint any dependencies:

namespace App\Console\Commands;

use App\User;
use App\Repositories\UserRepository;
use Illuminate\Console\Command;

class PruneUsers extends Command
    protected $signature = 'prune:users';

    protected $description = 'Prune inactive users';

    public function __construct()

    public function handle(UserRepository $userRepository)

As we can see, Artisan commands can be very handy for automating certain tasks, and/or having quick and clean access to functionality both via the scheduler and the CLI. Being familiar with the Artisan CLI can increase productivity and help developers clean up their code.


Laravel queues enable apps to offload lengthy tasks (sending emails, generating reports, etc.) to the background for processing, which significantly increases app responsiveness. While this topic is very comprehensive, we’re going to give a brief overview of how queues work and how to use them.

Laravel supports several different queue drivers out of the box: database, Beanstalkd, SQS, and Redis. For local testing, there is also a sync driver, which executes tasks immediately. Usually, developers will opt for one of these built-in connection options and configure several different queues (“lanes” that tasks can be dispatched into) on it—for example, one might have an emails queue and a reportGeneration queue. All of this is configured within the /config/queue.php configuration file.

With the configuration all set, we can start sending jobs onto queues. A Job is a dispatchable class, which just means it implements the Illuminate\Contracts\Queue\ShouldQueue contract so it can be easily sent off to a queue by calling the dispatch() method on it. Once in a queue, a job’s handle() method will be called—this is where the task-processing logic is coded.

Jobs are created by running the make:job Artisan command:

php artisan make:job GenerateReport

Let’s look at an example of a Job class:

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class GenerateReport implements ShouldQueue
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $reportData;

    public function __construct($reportData)
        $this->reportData = $reportData;

    public function handle()
        // Generate Report logic...

Now, let’s dispatch it:

class ReportController extends Controller
    public function generate(Request $request)

        return response()->json(204);

Once the job has been dispatched onto the queue, it will get picked up by a queue worker. Queue workers are long-lived processes that hold the current Laravel application state in memory, and process jobs in queues. This means they will not pick up any code changes to your application, so you’ll need to restart queue workers each time you update the application. Queue workers are started and restarted with the following Artisan commands:

# start
php artisan queue:work

# restart
php artisan queue:restart

Queues can be a very powerful tool in a Laravel developer’s arsenal. Therefore, the candidate should be familiar with how and when queues should be used, and the basic difference between supported queue drivers.

Task Scheduling

Instead of having to ssh into a server and define a separate cron job for every recurring task, Laravel’s task scheduler allows developers to do so within PHP code using a fluent API. To make this work, all that’s needed is to set up a single cron job on the server that runs the php artisan schedule:run command every minute. Laravel will leverage this cron job to assess the app’s defined tasks and run the ones that are due.

Scheduled tasks are defined in the schedule method of the App\Console\Kernel class. The scheduler can run Artisan commands, queued jobs, or any other arbitrary piece of code:

protected function schedule(Schedule $schedule)
    // Run an artisan command once a week

    // Queue a GenerateReport job once a day
    $schedule->job(new GenerateReport)->daily();

    // Execute arbitrary code every 30 minutes
    $schedule->call(function () {

        // Code...


The Laravel scheduler also supports various commands for defining the frequency at which the tasks will execute, like daily() and weekly() used in our examples.

Laravel’s task scheduler is a very convenient tool for “porting” cron job definitions from the server to the app codebase, and therefore allowing developers to keep them within a project’s version control system (VCS)—e.g., Git, as well as making them more readable and elegant. Developers who know Laravel’s ins and outs will also know how to use the features offered by the task scheduler to their advantage.


Interacting with a database in Laravel is very simple, using either the Illuminate\Support\Facades\DB facade, or the Eloquent ORM. We’ll talk about Eloquent in a bit; for now, let’s look at how we can run raw SQL queries using the DB facade:

// Select statement
DB::select('select * from posts where id > ?', [20]);

// Insert statement
DB::insert('insert into posts (title, slug, content) values (?, ?)', ['First Post', 'first-post', 'Lorem ipsum content']);

// Insert statement
DB::update('update posts set content = ? where slug = ?', ['Dolor sit amet content', 'first-post']);

// Delete statement
DB::delete('delete from posts where slug = ?', ['first-post']);

// General statement
DB::statement('ALTER TABLE posts CHANGE title post_title VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci');

Running raw SQL statements is sometimes required, but most times, there’s a better, more readable, API-based way of generating SQL queries: the query builder. Laravel’s query builder offers a convenient way of generating almost any query an application will ever need. Let’s look at the most commonly used methods:

// get() method returns a Collection of results
$posts = DB::table('posts')->get();

// where() method appends the WHERE clause
$posts = DB::table('posts')->where('id', '>', 20)->get();

// first() method returns the first result as an
// stdClass object that matches the criteria
$posts = DB::table('posts')->where('slug', 'some-post')->first();

// orderBy() method sorts the results
$posts = DB::orderBy('created_at', 'desc')->get();

// insert() method inserts a row into the database
    ['title' => 'Some Post Title', 'slug' => 'some-post',
     'content' => 'Lorem ipsum content']

// update() method updates a row in the database
DB::table('posts')->where('slug', 'some-post')
    ->update(['content' => 'Dolor sit amet content']);

// delete() method deletes rows from the database
DB::table('posts')->where('active', 0)->delete();

Here’s a few more examples of what can be achieved using the query builder:

// Select all posts which are either active, 
// or created by users with id 1, 2 or 3
$posts = DB::table('posts')->where('is_active', 1)
    ->orWhereIn('user_id', [1, 2, 3])->get();

// Skip 20 posts, then select the next 10
$posts = DB::table('posts')->skip(20)->take(10)->get();

// Join posts with users, and select all columns from 
// 'posts' table, and 'name' from 'users' table
$posts = DB::table('posts')
    ->join('users', 'posts.user_id', '=', '')
    ->select('posts.*', '')->get();

Another convenient feature provided by the DB facade are transactions. A transaction ensures that either all of the queries within it are executed, or none are. There are two ways to use transactions: closure-based and manually:

// Closure based
DB::transaction(function () {
    DB::table('users')->where('id', 5)->update(['active' => 1]);
    DB::table('users')->where('active', 0)->delete();

// Manual
try {

    DB::table('users')->where('id', 5)->update(['active' => 1]);
    DB::table('users')->where('active', 0)->delete();

} catch (\Exception $e) {

The closure-based approach is generally simpler to use because it automatically handles committing its transaction, or rolling it back in case of an exception. But the manual approach can offer more control in situations where that’s needed.

One last, but definitely not least, feature to mention is database migrations. Migrations are classes which allow for version-controlling database changes—everything from creating tables and columns, deleting them, and updating them. They consist of up and down methods. The up method is run when first changing the database in some way (creating a table, column, etc.), and the down method is used for reverting those changes.

Migration classes are generated by running the make:migration Artisan command:

php artisan make:migration create_posts_table 

Artisan places the migration files in the /database/migrations folder. A migration file will include various column types (string, text…) and modifiers (index, nullable…):

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePostsTable extends Migration
    public function up()
        Schema::create('posts', function (Blueprint $table) {

    public function down()

Migrating the database is done via the migrate Artisan command:

php artisan migrate 

This will run the up method in the migration classes. Rolling back the migrations is done by running:

php artisan migrate:rollback 

This will run the down method in the migration classes.

While options for interaction with databases are plentiful in Laravel, the API is fairly easy to remember, which makes for an efficient developer experience. Having a deep understanding of how the database API works is very important for Laravel application development, as it affects both productivity and code quality.

Eloquent: Laravel’s Object-relational Mapper (ORM)

Eloquent is likely the most complex component of the Laravel framework, but its basic functionalities are the foundation for using Laravel proficiently. Each Eloquent model maps directly to a database table, and each Model object maps to a database row in that table. This, and the fact that it uses Laravel’s query builder underneath to generate SQL queries makes it an invaluable tool for interacting with the database in an object-oriented way.

All Eloquent models must extend the Illuminate\Database\Eloquent\Model class, and the easiest way to create them is by using the make:model Artisan command:

php artisan make:model Post

This will generate the following class in the app folder:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model

By default, the table name the model corresponds to will be auto-guessed by Eloquent—for our particular example, a posts table. If developers wish to define a custom table name, they can use the protected $table property on the model.

Now that we have our model and know which database table it will use, let’s look at the most common operations we can perform, using the same fluent API as we did with the query builder:

// Return a collection of Post objects
$posts = Post::all();
$posts = Post::where('active', 1)->get();
$posts = Post::where('user_id', 5)->orderBy('created_at', 'desc')->get();

// Return a single Post object
$post = Post::find(5);
$post = Post::where('slug', 'some-post')->first();

// Create a Post object
$post = Post::create(['title' => 'Some Post', 'slug' => 'some-post', 'content' => 'Lorem ipsum content']);

// Update the Post object
Post::where('slug', 'some-post')->update(['content' => 'Dolor sit amet content']);

// Alternatively
$post = Post::where('slug', 'some-post')->first();
$post->content = 'Dolor sit amet content';

// Delete the matching Post objects
Post::where('active', 0)->delete();

// Alternatively, delete a Post object
$post = Post::where('slug', 'some-post')->first();

Another powerful feature of the Eloquent ORM is managing table relationships. For example, a Category model may have many Posts, and a Post may belong to a User. Eloquent provides a fluent API for managing several types of relationships:

  • One to one
  • One to many
  • Many to many
  • Has one through
  • Has many through
  • One to one (polymorphic)
  • One to many (polymorphic)
  • Many to many (polymorphic)

We’re going to take a look at the most commonly used relationships:

  • One to many (a Category has many Posts)
  • Its inverse, belongs to (each Post belongs to a single User)
  • Many to many (a Post has many Tags, and a Tag can belong to many Posts)

Relationships are defined as methods that return a relationship instance. Let’s define the one to many Posts relationship on our Category model:

class Category extends Model
    public function posts()
        return $this->hasMany('App\Post');

For this relationship to work, we must have a category_id column in our posts table, which is automatically assumed by Laravel by concatenating category (snake-cased name of the Category model) and adding _id to it. Now we can call the relationship:

$categoryPosts = $category->posts()->get();

This will return a collection of Posts that belong to the given Category. Alternatively, we can achieve the same result by calling posts as a dynamic property instead of the posts() method. Eloquent is smart enough to map the posts dynamic property to the relationship method and return the desired Posts collection:

$categoryPosts = $category->posts;

We can also apply filters, same as we did before:

$activePosts = $category->posts()->where('active', 1)->orderBy('created_at', 'desc')->get();

If we want to find out which User owns a certain Post, we can define inverse of the hasMany relationship (belongsTo) on our Post model:

class Post extends Model
    public function user()
        return $this->belongsTo('App\User');

Let’s call it:

$user = $post->user;

Once again, notice we’re not calling the user() method, but rather the user dynamic property.

The many to many relationship requires an additional, intermediary (pivot) table. If we want our Posts to have many Tags, and the Tags to belong to many Posts, then our intermediary table will need to be named post_tag (snake-cased models ordered alphabetically) and have post_id and tag_id columns.

Now we can define relationships on the models:

class Post extends Model
    public function tags()
        return $this->belongsToMany('App\Tag');

class Tag extends Model
    public function posts()
        return $this->belongsToMany('App\Post');

This allows us to find all the Tags for a given Post:

$postTags = $post->tags()->get();

And all the Posts a given Tag belongs to:

$tagPosts = $tag->posts()->get();

Eloquent also provides a way of optimizing database queries using eager loading. Eager loading solves the “N + 1 problem” when iterating over a collection of Model objects and querying their relationships. For example, let’s iterate over a category’s posts and display the name of each post’s user:

$category = Category::find(1);

foreach ($category->posts as $post) {
    echo $post->user->name;

If our category has 10 posts, this code segment will generate 12 queries. Let’s see what happens:


This will query the database for a Category with an ID of 1.

foreach ($category->posts ...

This will load the category’s posts into the Category object. Now comes the problematic part:


We have 10 posts, and each post will query the database to fetch the user who owns the post, so we can display their name. To fix this, we can eager load all the users for all of our category’s posts:

$category = Category::with('posts.user')->where('id', 1)->first();

By calling the with('posts.user') method, we can tell Eloquent that we want it to find all the posts belonging to the category with ID 1, then find all of their users in a single database query. This will result in a total of only three queries, regardless of the number of posts the category has:

  • One query for fetching the category
  • One query for fetching all of its posts
  • One query for fetching all of users for these posts

This sums up the basics of the Eloquent ORM. While Eloquent can do much more than what we’ve discussed here, this should be enough to evaluate how far the developer’s knowledge reaches. Understanding Eloquent is a must for every expert Laravel developer, so including Eloquent-related questions in the interview process will go a long way.


Laravel’s simplicity can be a double-edged sword. It can fool novice developers into thinking that writing code will always be a smooth experience, and nothing can ever go wrong. But that’s not how software development works: Once they hit a wall in the form of a deceiving bug, they’ll get lost and won’t know how to move forward. An error 500 screen will be presented to them, and they won’t know what the next step is toward the solution.

One of the most valuable traits of an experienced developer is being proficient in debugging. When it comes to Laravel, there are several methods to debug an application.

The first thing the developer should be aware of is the debug option in the config/app.php config file: When set to true, after hitting an exception in code, the exception details screen will be shown in the browser, instead of a blank error 500 page. This will, in most cases, point us to the exact line of code where the exception happened and provide a lot of helpful information about the exception. All of this information will also be written to the storage/logs/laravel.log log file, regardless of the value of the debug option.

Another great way to debug is using the Laravel Debugbar library. Apart from displaying all of the exception-related data, Debugbar will also provide information about request data, session data, and executed database queries, which can speed up debugging considerably. Other debugging libraries worth mentioning are Laravel Telescope and the Clockwork browser extension.

How to Hire the Best Laravel Developers: Experience with Best Practices Is Key

Finding and hiring a great Laravel freelancer can definitely be a tall order. The simplicity of the Laravel framework and its excellent, easy-to-understand documentation make it easy for almost any developer to pick it up quickly, regardless of their experience. This can make it difficult to weed out novice developers and find the right fit for your team.

Our Laravel hiring guide provides enough knowledge and understanding of how Laravel web development works to help recognize skilled developers in the USA or abroad and make the right choice for their business needs. However, not knowing everything mentioned in this guide off the top of their head does not necessarily mean the developer is not good at what they do. And vice-versa: Memorizing all of Laravel’s documentation without any practical years of experience does not make a developer great.

With this guide on how to hire dedicated Laravel developers, interviewers can readily craft the relevant interview questions they need, evaluate their candidates’ knowledge, and make the final call within their normal hiring models. Best of luck!

Top Laravel Developers are in High Demand.

Start Hiring