Laravel has many undocumented features, and unfortunately, unless you're dedicating large chunks of time to digging through the source code, and tracking what's going on, these features are likely to pass you by. Fortunately, I have done, and continue to do, exactly that. So in this article, I'll be going over middleware priority, which is something that is only briefly mentioned in the documentation.
Within Laravel's documentation there's an entry called Middleware Sorting, which explains how you can set the priority of middleware. There's also another entry called URL Defaults and Middleware Priority which touches a bit more on this. Unfortunately, these two entries don't fully cover the topic, both what it is, and the features available to you.
Laravel and Middleware Priority
Different people will think of different things when they hear the term 'priority', especially since it's typically used when referring to code that orders other code based on a numeric value. This is essentially what Laravel does for middleware, though the value is implied based on where the middleware appears.
This is the default middleware priority.
protected $middlewarePriority = [
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Contracts\Session\Middleware\AuthenticatesSessions::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
When it comes time to execute the route middleware, the list of middleware to be executed is sorted based on the order in which they appear in the priority list. If a piece of middleware should appear before another, but doesn't, it is moved to the position directly before it. In all other cases, the middleware is left exactly as it is.
Partially Unpredictable
Because of how this works, the execution order of the middleware isn't fully predicatable. While it is certain that middleware will run before or after those that it should, providing they're in the priority list, which pieces of middleware will run before, in between or after those, isn't easily predictable.
Remember that beyond the priority reordering, middleware can be inherited from route groups, as well as being applied from individual routes, controllers, route groups and global middleware.
Global Middleware
Global middleware is not affected by the priority list. Since it is defined in one place, and all items within it are executed in the order they are defined, for every request, whether it has a route or not, priority is easily achieved by modifying that list.
Setting the Priority
As covered in Laravels documentation, it is possible to set the priority list for middleware. The downside to
this is that its destructive, and will overwrite the previous value, meaning that your code needs to be aware
of any previous modifications using this approach. As such, it's recommended to avoid this approach unless you
absolutely need to
overwrite the priority. This is of course achieved using the
Middleware::priority method when configuring
the middleware in the bootstrap/app.php file.
->withMiddleware(function (Middleware $middleware): void {
$middleware->priority([
\Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Illuminate\Auth\Middleware\Authorize::class,
]);
})
Adding to the Priority
Laravels documentation very briefly covers one of the possible ways to do this, but there are a handful, and it depends how you want to go about it.
Appending to the Priority
If you want to append a piece of middleware to the end of the priority list as it exists at that time, you have
to call a method on the HTTP kernel, specifically the
Kernel::appendToMiddlewarePriority
method.
$kernel->appendToMiddlewarePriority(MyNewMiddleware::class);
Appending Relative to Other Middleware
However, if you want to append a piece of middleware relative to other middleware, you can use the
Middleware::appendToPriorityList
method when configuring your middleware.
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToPriorityList(
after: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
append: MyMiddleware::class,
]);
})
This will ensure that the middleware MyMiddleware appears after the
middleware \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests
in the priority list.
Default Placement
If the middleware you wish yours to be added after isn't in the list, yours will be added to the end.
This method maps to Kernel::addToMiddlewarePriorityAfter,
which can be called directly if you need to do this outside of bootstrap/app.php.
$kernel->addToMiddlewarePriorityAfter(
after: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
middleware: MyMiddleware::class
);
Append After Multiple
Both of these methods accept an array as their first argument. If one is provided,
the middleware will be added to the priority list after all of the middleware in the array.
->withMiddleware(function (Middleware $middleware): void {
$middleware->appendToPriorityList(
after: [
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
],
append: MyMiddleware::class,
]);
})
$kernel->addToMiddlewarePriorityAfter(
after: [
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
],
middleware: MyMiddleware::class
);
Prepending to the Priority
If you instead want to prepend a piece of middleware, adding it to the start of the priority list, as it exists
at that time, you again need to call a method on the HTTP kernel, specifically the
Kernel::prependToMiddlewarePriority
method.
$kernel->prependToMiddlewarePriority(MyNewMiddleware::class);
Prepending Relative to Other Middleware
Again, if you want to prepend your middleware relative to another, you can use the
Middleware::prependToPriorityList
method when configuring your middleware.
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToPriorityList(
before: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
prepend: MyMiddleware::class,
]);
})
This will ensure that the middleware MyMiddleware appears before the
middleware \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests
in the priority list.
Default Placement
If the middleware you wish yours to be added before isn't in the list, yours will be added to the start.
This method maps to Kernel::addToMiddlewarePriorityBefore,
which can be called directly if you need to do this outside of bootstrap/app.php.
$kernel->addToMiddlewarePriorityBefore(
before: \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
middleware: MyMiddleware::class
);
Prepend Before Multiple
Both of these methods accept an array as their first argument. If one is provided,
the middleware will be added to the priority list before all of the middleware in the array.
->withMiddleware(function (Middleware $middleware): void {
$middleware->prependToPriorityList(
before: [
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
],
prepend: MyMiddleware::class,
]);
})
$kernel->addToMiddlewarePriorityBefore(
before: [
\Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
],
middleware: MyMiddleware::class
);
Middleware Syncing
Despite the Middleware configuration class and the
HTTP kernel being the ones that modify the priority list, the router is the one that actually makes use of it.
This is why all the methods on the HTTP kernel that modify this list, syncs the new priority to the router.
This does mean that you can change the priority at various points during your application's lifecycle,
however, it must be before the router has started processing the route.
Generally speaking, you're safe to modify the priority in the
register or
boot method in a service provider, but not within
middleware or controllers.
Prioritising the Priority List
The order that you append or prepend middleware to the priority list is important, as you can't append a piece of middleware relative to another, if that other isn't in the list yet. Because the append and prepend approaches have fallbacks, you may find yourself in an unfortunate situation if you get the order wrong.
Because of how the application builder handles the middleware configuration, the priority order
defined/customised in bootstrap/app.php will always be present by the time service
providers are registered.
The Hidden Parts of Laravel
Laravels middleware priority functionality is one of many parts of Laravel that is either hidden, or barely documented. I'll be covering more of these topics in coming articles, but for now I hope that you find this useful, whether as solution to a problem you're having, or as a way to learn more about Laravel.