Skip to main content
Middleware provides a convenient mechanism for filtering and inspecting HTTP requests entering your application. Each middleware can examine requests, perform operations, and either pass the request to the next middleware or terminate the request entirely.

How middleware works

Middleware forms a pipeline through which requests flow before reaching your application logic. Think of middleware as layers of an onion:
1

Request enters

The HTTP request enters through public/index.php and is captured by the framework.
2

Middleware pipeline

The request passes through each registered middleware in order. Each middleware can:
  • Inspect or modify the request
  • Perform operations (logging, authentication, etc.)
  • Pass the request to the next middleware
  • Terminate the request and return a response
3

Route handler

If all middleware passes, the request reaches the controller or route closure.
4

Response returns

The response travels back through the middleware stack in reverse order, allowing each middleware to inspect or modify the response.
Middleware executes in the order it’s registered for requests, and in reverse order for responses.

Configuring middleware

In Laravel 11+, middleware is configured in bootstrap/app.php using the fluent withMiddleware() method:
bootstrap/app.php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        //
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();
The withMiddleware() callback receives a Middleware instance that provides methods for customizing the middleware stack.

Middleware groups

Laravel includes predefined middleware groups that bundle common middleware together:
Applied to routes in routes/web.php, includes:
  • Session handling
  • CSRF protection
  • Cookie encryption
  • View sharing
bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        \App\Http\Middleware\CustomWebMiddleware::class,
    ]);
})
Applied to API routes, includes:
  • Rate limiting
  • Stateless sessions
bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->api(prepend: [
        \App\Http\Middleware\ApiVersioning::class,
    ]);
})

Global middleware

Global middleware runs on every request to your application:
bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->append(\App\Http\Middleware\LogRequests::class);

    // Or prepend to run before framework middleware
    $middleware->prepend(\App\Http\Middleware\SecurityHeaders::class);
})
Be cautious when adding global middleware, as it affects performance on every request. Only include middleware that truly needs to run globally.

Route middleware

Apply middleware to specific routes or route groups:
routes/web.php
use Illuminate\Support\Facades\Route;

// Single middleware
Route::get('/dashboard', function () {
    //
})->middleware('auth');

// Multiple middleware
Route::get('/admin', function () {
    //
})->middleware(['auth', 'admin']);

// Middleware groups
Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('/profile', function () {
        //
    });

    Route::get('/settings', function () {
        //
    });
});

Middleware aliases

Register middleware aliases for cleaner route definitions:
bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'subscribed' => \App\Http\Middleware\EnsureUserIsSubscribed::class,
    ]);
})
Then use the alias in routes:
routes/web.php
Route::get('/admin/dashboard', function () {
    //
})->middleware('admin');

Creating custom middleware

Generate middleware using Artisan:
php artisan make:middleware EnsureTokenIsValid
This creates a middleware class in app/Http/Middleware/:
app/Http/Middleware/EnsureTokenIsValid.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class EnsureTokenIsValid
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        if ($request->input('token') !== 'my-secret-token') {
            return redirect('home');
        }

        return $next($request);
    }
}

Before middleware

Perform operations before the request reaches the application:
app/Http/Middleware/LogRequest.php
public function handle(Request $request, Closure $next): Response
{
    // Perform action before request
    Log::info('Request URL: ' . $request->url());
    Log::info('Request Method: ' . $request->method());

    return $next($request);
}

After middleware

Perform operations after the application generates a response:
app/Http/Middleware/AddSecurityHeaders.php
public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);

    // Perform action after response
    $response->headers->set('X-Frame-Options', 'DENY');
    $response->headers->set('X-Content-Type-Options', 'nosniff');
    $response->headers->set('X-XSS-Protection', '1; mode=block');

    return $response;
}

Before and after middleware

Combine both patterns:
app/Http/Middleware/PerformanceMonitor.php
public function handle(Request $request, Closure $next): Response
{
    $startTime = microtime(true);

    // Before: Log request start
    Log::debug('Request started', ['url' => $request->url()]);

    $response = $next($request);

    // After: Log execution time
    $executionTime = microtime(true) - $startTime;
    Log::debug('Request completed', [
        'url' => $request->url(),
        'time' => $executionTime,
    ]);

    return $response;
}

Middleware parameters

Pass additional parameters to middleware:
app/Http/Middleware/CheckRole.php
public function handle(Request $request, Closure $next, string $role): Response
{
    if (! $request->user()->hasRole($role)) {
        abort(403, 'Unauthorized');
    }

    return $next($request);
}
Specify parameters in routes using colons:
routes/web.php
Route::get('/admin/posts', function () {
    //
})->middleware('role:admin');

Route::get('/editor/posts', function () {
    //
})->middleware('role:editor');

// Multiple parameters
Route::get('/premium', function () {
    //
})->middleware('subscription:premium,active');

Common middleware patterns

app/Http/Middleware/EnsureAuthenticated.php
public function handle(Request $request, Closure $next): Response
{
    if (! $request->user()) {
        return redirect()->route('login');
    }

    return $next($request);
}
app/Http/Middleware/EnsureUserHasRole.php
public function handle(Request $request, Closure $next, string $role): Response
{
    if (! $request->user()?->hasRole($role)) {
        abort(403, 'This action is unauthorized.');
    }

    return $next($request);
}
app/Http/Middleware/SanitizeInput.php
public function handle(Request $request, Closure $next): Response
{
    $input = $request->all();

    array_walk_recursive($input, function (&$value) {
        $value = strip_tags($value);
    });

    $request->merge($input);

    return $next($request);
}
app/Http/Middleware/ForceJsonResponse.php
public function handle(Request $request, Closure $next): Response
{
    $request->headers->set('Accept', 'application/json');

    return $next($request);
}
app/Http/Middleware/ThrottleApiRequests.php
use Illuminate\Cache\RateLimiter;

public function handle(Request $request, Closure $next): Response
{
    $key = $request->ip();

    if (app(RateLimiter::class)->tooManyAttempts($key, 60)) {
        abort(429, 'Too many requests');
    }

    app(RateLimiter::class)->hit($key);

    return $next($request);
}

Terminable middleware

Perform work after the response has been sent to the browser:
app/Http/Middleware/TerminableMiddleware.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class TerminableMiddleware
{
    public function handle(Request $request, Closure $next): Response
    {
        return $next($request);
    }

    public function terminate(Request $request, Response $response): void
    {
        // Perform actions after response is sent
        // This doesn't delay the response to the user
        Log::info('Response sent', [
            'status' => $response->getStatusCode(),
            'url' => $request->url(),
        ]);
    }
}
Terminable middleware is perfect for logging, metrics collection, or cleanup tasks that don’t need to delay the response.

Middleware priority

Control the order middleware executes:
bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
    $middleware->priority([
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \App\Http\Middleware\CustomMiddleware::class,
    ]);
})

Best practices

1

Keep middleware focused

Each middleware should have a single responsibility. Don’t create “kitchen sink” middleware that does too many things.
2

Use dependency injection

Inject dependencies through the constructor:
public function __construct(
    protected RateLimiter $limiter,
    protected Logger $logger
) {}
3

Fail fast

If a middleware needs to reject a request, do it early to avoid unnecessary processing:
public function handle(Request $request, Closure $next): Response
{
    if (! $this->isValidRequest($request)) {
        abort(403);
    }

    return $next($request);
}
4

Test middleware in isolation

Write unit tests for middleware logic:
public function test_middleware_blocks_unauthenticated_users(): void
{
    $request = Request::create('/admin', 'GET');
    $middleware = new AuthMiddleware();

    $response = $middleware->handle($request, fn() => response('OK'));

    $this->assertEquals(302, $response->getStatusCode());
}
Avoid storing state in middleware properties between requests. Middleware instances may be reused, leading to unexpected behavior.
Use php artisan route:list to see which middleware is applied to each route in your application.

Build docs developers (and LLMs) love