Laravel Tutorials

Insert Middleware Before or After Existing Laravel Middleware

Published
Insert Middleware Before or After Existing Laravel Middleware image

Some middleware has to run at a specific point in Laravel's default stack. A middleware that reads the client IP needs to run after TrustProxies. A middleware that reads the session needs to run after StartSession, and one that changes how route models resolve needs to run before SubstituteBindings.

The withMiddleware() callback in bootstrap/app.php could add middleware to the start or the end of the global stack and of a group, but not next to a specific built-in middleware. Laravel 13.40 adds four methods for that, and lets the existing replace() methods take an array.

Inserting Next to a Built-in Middleware

The insertBefore() and insertAfter() methods work on the global middleware stack. The first argument is the middleware to find, and the second is one middleware or an array of them:

use App\Http\Middleware\BlockBannedIps;
use App\Http\Middleware\SanitizeInput;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
use Illuminate\Http\Middleware\TrustProxies;

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfter(TrustProxies::class, BlockBannedIps::class);

    $middleware->insertBefore(TrimStrings::class, SanitizeInput::class);
})

The insertBeforeInGroup() and insertAfterInGroup() methods do the same inside a middleware group. They take the group name first:

use App\Http\Middleware\SetLocaleFromSession;
use Illuminate\Session\Middleware\StartSession;

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfterInGroup('web', StartSession::class, SetLocaleFromSession::class);
})

The group methods work on any group, including ones you define with $middleware->group().

Replacing With an Array

The new methods are built on a smaller change from the same contributor. The replace() and replaceInGroup() methods now accept an array, and the array takes the place of the original middleware in the stack. To insert next to a middleware, include it in the array:

// Run StripCrossOriginReferer after TrustProxies
$middleware->replace(TrustProxies::class, [
    TrustProxies::class,
    StripCrossOriginReferer::class,
]);

// Replace one middleware with two
$middleware->replace(Foo::class, [Bar::class, Baz::class]);

The web() and api() helpers pass their replace argument to replaceInGroup(), so arrays work there too:

$middleware->web(replace: [
    StartSession::class => [StartSession::class, SetLocaleFromSession::class],
]);

Example: Locale From the Session

Some applications store a user's language in the session and translate URL slugs. A /fr/articles/{article} route might resolve the article by its French slug, with a model that reads the current locale in resolveRouteBinding():

class Article extends Model
{
    public function resolveRouteBinding($value, $field = null)
    {
        return $this->where('slug->'.app()->getLocale(), $value)->firstOrFail();
    }
}

The locale has to be set after the session starts and before SubstituteBindings resolves the model. The default web group runs in this order:

  1. EncryptCookies
  2. AddQueuedCookiesToResponse
  3. StartSession
  4. ShareErrorsFromSession
  5. PreventRequestForgery
  6. SubstituteBindings

Before 13.40, neither helper put the middleware in the right place. With web(append: ...), the middleware runs after SubstituteBindings, so the model is looked up with the default locale and a French slug returns a 404. With web(prepend: ...), it runs before StartSession, and $request->session() throws Session store not set on request.

The workaround was to redefine the whole group:

$middleware->group('web', [
    EncryptCookies::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    SetLocaleFromSession::class,
    ShareErrorsFromSession::class,
    PreventRequestForgery::class,
    SubstituteBindings::class,
]);

That copy does not follow framework changes. Laravel 12 used ValidateCsrfToken in this group, and Laravel 13 uses PreventRequestForgery, so an application that copied the Laravel 12 group would keep the old class after upgrading. The copied list also drops auth.session, which Laravel adds to the group when you call $middleware->authenticateSessions().

With 13.40, you only name the middleware you are adding:

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfterInGroup('web', StartSession::class, SetLocaleFromSession::class);
})

The middleware reads the session and sets the locale:

class SetLocaleFromSession
{
    public function handle(Request $request, Closure $next): Response
    {
        if ($locale = $request->session()->get('locale')) {
            App::setLocale($locale);
        }

        return $next($request);
    }
}

The resulting group is EncryptCookies, AddQueuedCookiesToResponse, StartSession, SetLocaleFromSession, ShareErrorsFromSession, PreventRequestForgery, SubstituteBindings.

Example: The Client IP

The global stack has the same problem. A middleware that checks $request->ip() against a list of banned addresses gets the load balancer's address if it runs before TrustProxies, and prepend() puts it before TrustProxies. With append(), it runs after HandleCors, the maintenance mode check, and the input middleware.

Insert it directly after TrustProxies instead:

$middleware->insertAfter(TrustProxies::class, BlockBannedIps::class);
class BlockBannedIps
{
    public function handle(Request $request, Closure $next): Response
    {
        if (in_array($request->ip(), config('security.banned_ips'), true)) {
            abort(403);
        }

        return $next($request);
    }
}

Further Reading

Jamie York contributed the array support for replace() in #62230 and the insert methods in #62261.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

serpapi logo
SerpApi

The Web Search API for Your LLM and AI Applications

Visit SerpApi

The latest

View all →
Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys image

Laravel Herd 2: Coding Agents, Workspaces, and Cloud Deploys

Read article
Strict Request Input Casting in Laravel image

Strict Request Input Casting in Laravel

Read article
Prevent Duplicate File Uploads With Laravel Storage Dedupler image

Prevent Duplicate File Uploads With Laravel Storage Dedupler

Read article
Slow Job Alerts and Replica Commit IDs in Laravel 13.40 image

Slow Job Alerts and Replica Commit IDs in Laravel 13.40

Read article
Flock: A Free, Native Herd Alternative for Laravel image

Flock: A Free, Native Herd Alternative for Laravel

Read article
Live Props for Real-Time Updates in Inertia.js v3.11 image

Live Props for Real-Time Updates in Inertia.js v3.11

Read article