As I'm sure you heard if you follow along with the Laravel community, the latest Laracon event was held on Wednesday, which was hosted online instead of the usual summer conference in the US. There were 15 fantastic speakers, but there was one that everybody was waiting for.

Taylor Otwell take to the (virtual) stage to announce the features coming to the next release of the framework, Laravel 8. He started out with some smaller "quality of life" changes, before really taking it up a level and introducing a raft of improvements and new features coming to Laravel 8 when it's released, on September 8th 2020.

Let's dive straight in and take a look at what was announced.

New landing page

The page that gets displayed when you hit the homepage on a fresh install has had a facelift and is now built with TailwindCSS and comes in light / dark versions. It has links to various SaaS products from Laravel, as well as community sites. There is also a link to the new Laravel shop if you want to get your hands on some merch.

Default app/models directory

Laravel 8 will now ship with an app/models directory, rather than leaving model class in the root app directory as in previous versions of Laravel. According to a poll run by Taylor, over 80% of developers were creating an app/models directory themselves anyway.

If you use any of the artisan generator commands that create a model class for you, such as php artisan make:model - it will of course create you a new model inside the new app/models directory. However, if you prefer to keep your models in your app directory and delete the new models directory, the generator commands will respect that and create the model classes in your app directory.

Controllers namespace prefix removed

In previous versions of Laravel, there was a property in the RouteServiceProvider.php called $namespace that was used to prefix the namespace of your controllers automatically (applying App\Http\Controllers). If you were using the callable syntax in your web.php routes file, Laravel may have double prefixed your namespaces. This property has been removed in Laravel 8, so you can import your controller classes in your routes file without issue.

Route caching improvements

Hopefully you were already using route caching in your application (running php artisan route:cache generates a PHP file with an array containing all of your routes, which Laravel then uses for routing as it's faster than having to parse your routes file(s) on every request). However, up until now, if you had any closures in your routes, or even if a package registered a route with a closure, that would have caused the route caching to fail.

Not anymore with Laravel 8 - it now supports route caching for closure based routes! Now you have no excuses not be using route caching.

Blade component attributes

If you extended a Blade component (e.g. having a component called DangerButton, that extended another component called Button) in Laravel 7, the child button wouldn't have had the $attributes passed down to it. That has changed in Laravel 8 - all child components will the $attributes available, making it easier to build extended components.

Cleaner syntax for closure based event listeners

In previous Laravel versions, when registering a closure based event listener, you had to first define the event class, then register the closure, and probably type hint the event class for the closure e.g.:

Event::listen(OrderShipped::class, function(OrderShipped $event) { 
    // Do something
});

In Laravel 8, you'll be able to skip the first definition of the event class as the framework will be able to infer it from the type hinted argument - e.g.:

Event::listen(function(OrderShipped $event) { 
    // Do something
});

Queueable anonymous event listeners

In Laravel 8, you will be able to send a closure based job to the queue from your model event callbacks e.g.:

class User extends Model {

    protected static function booting() 
    {
        static::created(queueable(function(User $user) {
           // Do something in the background
        }))
    }
    
}

In previous Laravel versions, it was not possible to do this unless you created an event class and event listener, using the ShouldQueue trait. This new feature makes it quicker and actually introduces the first namespaced function into the Laravel framework, Illuminate\Events\queueable.

Maintenance mode: secret access

Currently, if you use the artisan down / artisan up commands to put your site into maintenance mode but want to give some people access (e.g. yourself, other developers etc) the only way to do this is via IP whitelisting. That doesn't work well if you want to grant access to lots of people, or if you have a dynamic IP address that regularly changes. That's changing in Laravel 8!

You'll now able to use a "secret" flag when putting your site into maintenance mode - e.g:

php artisan down --secret=let-me-in

The value of the secret flag becomes a route (whilst your site is in maintenance mode). If you were to then navigate to that (using the above example, https://www.demo.com/let-me-in), the framework will set a cookie (that instructs your app to ignore the maintenance mode when you access it, lasting for several hours) and redirect you to the homepage of your app, which you can then browse as normal.

Maintenance mode: pre-rendered page

In Laravel 7 and below, if you run php artisan down to put your site into maintenance mode whilst deploying your application and then run Composer as part of the deployment, your application may still throw errors while your dependencies are changed and the autoload file is written - which means end users will see an error page, rather than the maintenance mode page.

That's solved in Laravel 8! You can now pass the name of a view to a "render" flag as part of the artisan down command, e.g.:

php artisan down --render="errors::back-soon"

Laravel will pre-render the view (in this case errors/back-soon.blade.php) and then put the site into maintenance mode. If anyone then attempts to access the site, they will see the pre-rendered view - the framework won't even attempt to load the composer autoload file, meaning errors won't be thrown. Nice!

Queued closure error handling

Whilst it's been possible to send a closure to the queue for background process for sometime, handling the failure of these jobs wasn't ideal. Whilst it would go to the failed jobs table in your database, you couldn't execute any other code once the failure occurred. Now in Laravel 8, you'll be able to register a callback to run when the job has failed, via a catch() method - similar to the failed() method on a standard job class. E.g.:

dispatch(function() {
    // Do something...
})->catch(function(Throwable $e) {
    // React to / handle the error
});

Exponential backoff for job failures

Starting with Laravel 8, you can add a new backoff() method to your job classes that returns an array of integers to decide the wait time between job attempts if it fails - e.g:

class ExampleJob
{
    //
    
    public function backoff()
    {
        return [1, 5, 10];
    }
    
    //
}

In this example, if the job failed on its first attempt, it would wait 1 second before try again. If the job then failed on the second attempt, it would wait 5 seconds before attempting again. Then if the job failed on its third (or more) time, there would be a wait of 10 seconds before it was attempted again. This could be very handy if you're working with a rate limited API to increase the wait each time the job fails.

Queued job batching

Job batching is a new powerful feature coming with Laravel 8. It will let you dispatch multiple jobs to the queue at once (in a "batch") to be processed simultaneously (assuming you have enough queue workers running) and register callbacks to fire for when all of the jobs have finished. There are 3 callbacks available:

  • then() - this will fire when all jobs in the batch have completed successfully
  • catch() - this will fire on the first (if any) job failure in the batch
  • finally() - this will fire when all jobs in the batch have finished executing (some may have completed successfully, some may have failed)

All of the callback methods have access to a $batch object, which contains various methods - such as checking the status, determining if there have been failures, cancelling the batch etc. This could be used in the catch() callback for example - stopping the rest of the batch from being processed when an error occurs.

This is how you can use the batch jobs:

Bus::batch([
    new ImportJob(),
    new ImportJob(),
    new ImportJob(),
    new ImportJob(),
    new ImportJob(),
])->then(function(Batch $batch){
    // All jobs completed successfully 
})->catch(function(Batch $batch) {
    // First job failure detected
})->finally(function(Batch $batch) {
    // All jobs have finished executing
})->dispatch();

There are also advanced features such as the ability to add other jobs to an existing  batch from within a job within that batch (via the $this->batch() helper in a job). This could be useful if you are processing large amounts of data and need to work in chunks.

If a job within your batch does fail, the rest of the batch will stop being processed and the 'catch()' method will be called. From within that, you could choose to resume the processing of the rest of batch if you decide the error isn't a problem. Alternatively, you can use a allowFailures() method instead of the catch() method when dispatching the batch and if there is a job failure, the rest of the batch will continue to be processed.

If you have the UUID of the batch, there is another helper method available on the Bus itself -  Bus::findBatch($batch_uuid);. This will return an object containing information about the batch - including the current progress. You could use this to show the progress of a batch in the UI of your application - possibly in near real time if you poll using AJAX.

Enhanced rate limiting

Starting in Laravel 8, you can now define multiple rate limiters via a new RateLimiter facade - giving them a name and a closure that returns the details of the limit. For example:

RateLimiter::for('authentication', function(Request $request) {
    return Limit::perMinute(50);
});

This can then be used with the existing throttle middleware in your routes file, but instead of passing an integer, you can now pass the name of a defined rate limiter. For example:

Route::get('/login')->middleware(['throttle:authentication']);

This would allow a maximum of 50 requests to the login page per minute - across all visitors as the page doesn't require authentication.

As the defined rate limiters use closures, you can write logic to determine the limit. Maybe if you have a /download route behind an authentication guard that each registered user could access no more than once per minute, but you have a group of premium customers that need unlimited access.

You can also segment the rate limit by any piece of data you want - maybe your rate limit applies per IP address, per user ID or per group of users.

Database schema dumping

If you're working on a large application that has a large number of database migrations, there is a new feature call schema dumping coming to Laravel 8 to help you tidy these up. You'll be able to run php artisan schema:dump - which will generate a SQL file in a database/schema directory, containing the full schema for your database as raw SQL.

When you run php artisan migrate, it will first look for a schema file to run, then any normal migration files.

By default, running that command won't delete your existing migration files. However, if you add a --prune flag to the command, it will delete all of your migration files, leaving you with the single schema file.

If you run the schema dump, then create new migrations, then run the command again, it will append the new migrations to the existing schema file.

It's supported for the most popular database drivers used with Laravel - MySQL, PostgreSQL and SQLite - although community effort may see support for other database engines such as MSSQL in the future.

Brand new Model factories!

The model factories feature of Laravel has been totally rewritten for Laravel 8, making them incredibly powerful and easier to work with. They are now class based, like some existing community packages.

For each model, you'll have a factory class, e.g. UserFactory, which contains a definition() method which returns an array of attributes, similar to the factories in Laravel 7 and earlier e.g.:

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name'  => $this->faker->name,
            'email' => $this->faker->safeEmail,
            'role'  => 'admin',
        ];
    }
}

Your models will make use of a new HasFactory trait, which provides a factory()  method on your model classes - which will return an instance of the factory class, on which you can call methods as before such as create() or make() - e.g:

use App\Models\User;

$fake_user = User::factory()->create();

You can of course optionally pass an array of attributes that you want to give a specific value to those methods, as you did in previous Laravel versions.

Because the factories are now class based, there is a fluent interface - for example if you wanted to create 10 users:

$fake_users = User::factory()->times(10)->create();

There are also improvements to factory states - rather than being closure based, you can now create your own custom methods right on the factory class itself. Being method based you can of course write whatever logic you wish (maybe passing in parameters), but here is a simple example of overriding an attribute:

class UserFactory extends Factory
{   
    public function withActiveStatus()
    {
        return $this->state(fn(array $attributes) => [
            'status' => 'active',
        ]);
    }    
}

Relationships with model factories have also been greatly improved in Laravel 8. The example below shows us creating a User model that has an Order:

$user_with_order = User::factory()
    ->has(Order::factory())
    ->create();

And because everything is fluent, you chain methods together such as:

$user_with_orders = User::factory()
    ->withActiveStatus()
    ->has(
        Order::factory()
            ->times(5)
            ->withStatus('shipped')
            ->state(function(array $attributes, User $user) {
                return ['customer_name' => $user->name];
            })
    )
    ->create();

In the example above, we create a User that has 5 Orders that all have a status of "shipped", with the customer_name attribute of the Order set to the name of our newly created fake User.

There are also magic methods available for working with relationships, meaning you can make your code even simpler. Sticking with the theme of the above example, here's an example of creating a User with 3 Orders using a magic method:

$user_with_orders = User::factory()
    ->hasOrders(3)
    ->create();

Those examples only cover one way of a "has many" relationship, but there are a whole range of methods (+ magic methods) available for the various other types of  model relationships.

If you are using factories in an existing Laravel project, you won't have to re-write them to the new format them right away - there is a separate official package to include support for the old format called laravel/legacy-factories. The package will also let you use both the old and new factories at the same time.

Laravel JetStream & Laravel Fortify

This isn't in the core of Laravel 8, but was such a big announcement  I couldn't just ignore it! It is a huge addition that deserves it's own post, but I'll try to summarise the best I can!

Previous versions of Laravel had a package called  laravel/ui whcih provided basic authentication scaffolding - giving you the front (Vue & Bootstrap 4) & back end code for a fully working login system for your application.

It's now time to say hello to Laravel JetStream - a new open source package for Laravel 8 to give you an even bigger head start with your applications.

It's a scaffolding package that will set you up with complete front & back end code for lots of common functionality in web applications, including:

  • Authentication (login, access a dashboard page, logout, password reset etc)
  • Two factor authentication (via an app such as Google Authenticator) with no setup, including features such as recovery codes
  • Browser session management (let a user see where else they are logged in, and force a logout of all other sessions)
  • API token management for users powered by Laravel Sanctum, with full customisable permissions (similar to oAuth scopes)
  • Full "teams" functionality (users can create teams, invite others users to teams, see other team members, manage roles (& permissions) of team members, switch between multiple teams etc)
  • User profile editing (let's users update their details) including profile photos (easily toggable on / off in config) and password changing
  • Delete account functionality
  • JavaScript powered front end (see below) for nice smooth interactions without full page reloads, giving a really slick feel

The front end CSS is built with Tailwind CSS, including components from the premium Tailwind UI library, giving it a superb look, but whilst remaining easy to customise.

When it comes to the rest of the front-end stack, there are two choices - you can opt to use Laravel Livewire & Alpine.js or Inertia (with Vue.js). They both give you the exact same features and front end UI - it just gives you the choice to work with your preferred tech stack.

All of the UI components (buttons, form controls, cards, modal windows etc) included in JetSream can be used elsewhere in your app, even interactive components that normally require JavaScript such as modals - saving you time! And as it's a scaffolding package (the files are published to your app), you have full control over customisation.

This really is a HUGE addition to the Laravel ecosystem - it has the potential to save developers so much time and let them focus on writing the business logic for their applications.

As mentioned earlier, Laravel JetStream will be a free, open source package and should be released around the same time as Laravel 8 (8th September 2020).


Phew, that's it. Laravel 8 has some fantastic new features - Taylor and the team certainly have been busy. I'm looking forward to getting my hands on it (+ Laravel JetStream) when it's released on 8th September 2020 - I can already see me benefiting from many of the new features - especially queued job batching.

Laravel 8 will require PHP 7.3 or above, and according to the recently published upgrade docs, will take around 15 minutes (if coming from 7.x). There are 17 breaking changes documented in the docs, of these, only 3 are considered to have a high likelihood of impact. If you don't fancy upgrading to laravel 8 manually, I'd highly recommend taking a look at the Laravel Shift service.

If you want to watch Taylor's talk yourself, or any of the other fantastic talks (I highly recommend watching those from Jack Ellis and Jonathan Reinink), you can purchase ($29 USD) the videos from the Laracon website (which actually uses Laravel JetStream!).


I hope you found this post on the new features in Laravel 8 useful - I'd love to get your feedback, or even just have a chat about what your favourite announcements are - you can find me @gbuckingham89 on Twitter.