Mastering Middleware Execution Order in Laravel 12
Laravel’s middleware provides a convenient mechanism for filtering HTTP requests entering your application. From authentication to CSRF protection, middleware acts as the guardians of your app, ensuring requests meet specific criteria before reaching your business logic. In Laravel 12, the order of middleware execution can make or break your application. This article dives deep into: Middleware execution order Middleware parameters Terminable middleware Managing the global middleware stack Let’s master the art of middleware. Understanding Middleware Execution Order Middleware in Laravel executes in the order they’re registered. Route::get('/profile', function () { // Your route logic })->middleware(['auth', 'verified', 'log-activity']); In this example: auth middleware runs first. Then verified. And finally log-activity. Sorting Middleware in Laravel 12 Sometimes, you need middleware to execute in a specific order — regardless of how they’re assigned to routes. Laravel 12 introduces the priority method in bootstrap/app.php: ->withMiddleware(function (Middleware $middleware) { $middleware->priority([ \App\Http\Middleware\ImportantFirstMiddleware::class, \App\Http\Middleware\MustRunSecondMiddleware::class, \App\Http\Middleware\ThirdInLineMiddleware::class, // Add more in the order you need ]); }); When to Use Middleware Priority Use it when: One middleware depends on another You want a consistent global execution order You have complex middleware chains with dependencies Real-World Example: Multi-Tenant App Suppose you're building a multi-tenant app with the following middleware: IdentifyTenant: detects which tenant the request belongs to SetupTenantDatabase: switches database ApplyTenantTheme: customizes UI styles Execution order is essential: ->withMiddleware(function (Middleware $middleware) { $middleware->priority([ \App\Http\Middleware\IdentifyTenant::class, \App\Http\Middleware\SetupTenantDatabase::class, \App\Http\Middleware\ApplyTenantTheme::class, ]); }); What Happens Without Proper Sorting? Expect: Broken features Data inconsistencies Security gaps Performance degradation Middleware Parameters Laravel allows middleware to receive dynamic parameters. Example: Subscription Check class RequireSubscription { public function handle(Request $request, Closure $next, string $level): Response { $user = $request->user(); if (!$user || !$user->hasSubscription($level)) { return response()->json([ 'error' => 'This feature requires a ' . ucfirst($level) . ' subscription', 'upgrade_url' => route('subscriptions.upgrade'), ], 403); } return $next($request); } } Usage Route::get('/premium-content', fn () => 'Premium!')->middleware('subscription:premium'); Class-Based Syntax Route::get('/analytics-dashboard', fn () => 'Analytics!') ->middleware(RequireSubscription::class.':business'); Multiple Parameters Route::get('/developer-tools', fn () => 'Dev tools') ->middleware('subscription:premium,business'); Real-World Example: Age-Based Content Filtering class ContentFilter { public function handle(Request $request, Closure $next, int $minimumAge = 13, bool $skipWarning = false): Response { $user = $request->user(); $userAge = $user?->age; if (!$userAge || $userAge route('content.restricted'); } session(['intended_url' => $request->fullUrl()]); return redirect()->route('content.warning', [ 'minimum_age' => $minimumAge, ]); } return $next($request); } } Usage Route::get('/forums')->middleware('content.filter'); Route::get('/mature-content')->middleware('content.filter:18,true'); Route::get('/teen-content')->middleware('content.filter:16,false'); Terminable Middleware: After Response Hooks Need to run logic after the response is sent? Use terminable middleware. Example: Page View Logger class TrackPageViews { public function handle(Request $request, Closure $next): Response { return $next($request); } public function terminate(Request $request, Response $response): void { if ($request->ajax() || $response->getStatusCode() !== 200 || $request->method() !== 'GET') { return; } DB::table('page_views')->insert([ 'path' => $request->path(), 'user_id' => $request->user()?->id, 'user_agent' => $request->userAgent(), 'ip_address' => $request->ip(), 'viewed_at' => now(), ]); } } Register as Singleton pu

Laravel’s middleware provides a convenient mechanism for filtering HTTP requests entering your application. From authentication to CSRF protection, middleware acts as the guardians of your app, ensuring requests meet specific criteria before reaching your business logic.
In Laravel 12, the order of middleware execution can make or break your application. This article dives deep into:
- Middleware execution order
- Middleware parameters
- Terminable middleware
- Managing the global middleware stack
Let’s master the art of middleware.
Understanding Middleware Execution Order
Middleware in Laravel executes in the order they’re registered.
Route::get('/profile', function () {
// Your route logic
})->middleware(['auth', 'verified', 'log-activity']);
In this example:
-
auth
middleware runs first. - Then
verified
. - And finally
log-activity
.
Sorting Middleware in Laravel 12
Sometimes, you need middleware to execute in a specific order — regardless of how they’re assigned to routes. Laravel 12 introduces the priority
method in bootstrap/app.php
:
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\App\Http\Middleware\ImportantFirstMiddleware::class,
\App\Http\Middleware\MustRunSecondMiddleware::class,
\App\Http\Middleware\ThirdInLineMiddleware::class,
// Add more in the order you need
]);
});
When to Use Middleware Priority
Use it when:
- One middleware depends on another
- You want a consistent global execution order
- You have complex middleware chains with dependencies
Real-World Example: Multi-Tenant App
Suppose you're building a multi-tenant app with the following middleware:
-
IdentifyTenant
: detects which tenant the request belongs to -
SetupTenantDatabase
: switches database -
ApplyTenantTheme
: customizes UI styles
Execution order is essential:
->withMiddleware(function (Middleware $middleware) {
$middleware->priority([
\App\Http\Middleware\IdentifyTenant::class,
\App\Http\Middleware\SetupTenantDatabase::class,
\App\Http\Middleware\ApplyTenantTheme::class,
]);
});
What Happens Without Proper Sorting?
Expect:
- Broken features
- Data inconsistencies
- Security gaps
- Performance degradation
Middleware Parameters
Laravel allows middleware to receive dynamic parameters.
Example: Subscription Check
class RequireSubscription
{
public function handle(Request $request, Closure $next, string $level): Response
{
$user = $request->user();
if (!$user || !$user->hasSubscription($level)) {
return response()->json([
'error' => 'This feature requires a ' . ucfirst($level) . ' subscription',
'upgrade_url' => route('subscriptions.upgrade'),
], 403);
}
return $next($request);
}
}
Usage
Route::get('/premium-content', fn () => 'Premium!')->middleware('subscription:premium');
Class-Based Syntax
Route::get('/analytics-dashboard', fn () => 'Analytics!')
->middleware(RequireSubscription::class.':business');
Multiple Parameters
Route::get('/developer-tools', fn () => 'Dev tools')
->middleware('subscription:premium,business');
Real-World Example: Age-Based Content Filtering
class ContentFilter
{
public function handle(Request $request, Closure $next, int $minimumAge = 13, bool $skipWarning = false): Response
{
$user = $request->user();
$userAge = $user?->age;
if (!$userAge || $userAge < $minimumAge) {
if ($skipWarning) {
return redirect()->route('content.restricted');
}
session(['intended_url' => $request->fullUrl()]);
return redirect()->route('content.warning', [
'minimum_age' => $minimumAge,
]);
}
return $next($request);
}
}
Usage
Route::get('/forums')->middleware('content.filter');
Route::get('/mature-content')->middleware('content.filter:18,true');
Route::get('/teen-content')->middleware('content.filter:16,false');
Terminable Middleware: After Response Hooks
Need to run logic after the response is sent? Use terminable middleware.
Example: Page View Logger
class TrackPageViews
{
public function handle(Request $request, Closure $next): Response
{
return $next($request);
}
public function terminate(Request $request, Response $response): void
{
if ($request->ajax() || $response->getStatusCode() !== 200 || $request->method() !== 'GET') {
return;
}
DB::table('page_views')->insert([
'path' => $request->path(),
'user_id' => $request->user()?->id,
'user_agent' => $request->userAgent(),
'ip_address' => $request->ip(),
'viewed_at' => now(),
]);
}
}
Register as Singleton
public function register(): void
{
$this->app->singleton(\App\Http\Middleware\TrackPageViews::class);
}
Use Cases
- Logging
- Dispatching jobs
- Cleanup tasks
- Performance monitoring
Managing Laravel’s Global Middleware
Global middleware can be configured in bootstrap/app.php
.
Set Custom Global Stack
->withMiddleware(function (Middleware $middleware) {
$middleware->use([
\Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,
\Illuminate\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Http\Middleware\ValidatePostSize::class,
\Illuminate\Foundation\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// Add your custom global middleware here
]);
});
Replace Middleware
$middleware->web(replace: [
\Illuminate\Session\Middleware\StartSession::class => \App\Http\Middleware\CustomSessionHandler::class,
]);
Remove Middleware
$middleware->web(remove: [
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
]);
Best Practices for Middleware
- Maintain a clear execution order
- Keep middleware single-purpose
- Group related middleware logically
- Optimize for performance
- Handle exceptions gracefully
- Test middleware chains thoroughly
Conclusion
Understanding and controlling middleware execution in Laravel 12 is essential for scalable, secure applications. Whether you’re handling authentication, localization, or tenant logic, middleware gives you both flexibility and power.
Mastering the priority()
method, parameterized middleware, and terminable middleware will help you build more robust, predictable applications.
A well-designed middleware strategy leads to cleaner code, better performance, and fewer bugs.