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

May 5, 2025 - 06:36
 0
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:

  1. auth middleware runs first.
  2. Then verified.
  3. 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.