skip to content

In Laravel 13, where do you register a custom middleware to run on every request, and how does that differ from attaching it to routes?

level: juniorimportance: must knowfreq 78%

answer

  1. no app/Http/Kernel.php in the app
  2. bootstrap/app.php, withMiddleware closure
  3. append() to the end, prepend() to the front
  4. ->middleware() on a route or group
  5. global runs before the route is matched

basics

~10 s

Global middleware is registered in bootstrap/app.php inside withMiddleware(), using $middleware->append() or prepend(); it wraps every request before routing. Route middleware is attached with ->middleware() on a route or group, by class name or alias.

solid answer

~40 s

Since Laravel 11 the app has no `app/Http/Kernel.php`; in Laravel 13 you configure middleware in `bootstrap/app.php` through `->withMiddleware(function (Middleware $middleware) { ... })`. `$middleware->append(SetBookingCurrency::class)` adds a class to the end of the global stack and `prepend()` to its front, so it wraps every HTTP request, including ones that end in a 404, because the global stack runs before the router matches a route. For middleware that only some routes need, you do not touch the global stack: you chain `->middleware(EnsureAgentIsVerified::class)` on the route, or on a route group, or give the class a short alias with `$middleware->alias()` and use that string. Route middleware is resolved after matching, can be excluded per route with `withoutMiddleware()`, and is ordered by the priority list; global middleware is none of those.

code

php · 14 lines
php
<?php

use App\Http\Middleware\AddCorrelationId;
use App\Http\Middleware\SetBookingCurrency;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(web: __DIR__.'/../routes/web.php', health: '/up')
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->prepend(AddCorrelationId::class);   // outermost layer
        $middleware->append(SetBookingCurrency::class);  // after the default global stack
    })
    ->create();

go deeper

for a junior

Recall the file and the closure: bootstrap/app.php, withMiddleware(), append() and prepend() for global, ->middleware() on a route for the rest.

for a middle

Explain that the global stack runs before routing, so no route, bound model or session exists yet, and that the web and api groups are separate from it.

for a senior

Argue placement: keep the global stack short, push browser-only concerns into the web group, and avoid global middleware that later needs per-route exceptions.

for a principal

Treat the global stack as a team-wide contract; require review for additions because every request, including health checks and 404s, pays for each entry.

## Where middleware registration lives in Laravel 13 A **middleware** in Laravel is a class whose `handle()` method receives the incoming `Illuminate\Http\Request` and a `$next` closure, and either passes the request on or answers it itself. Registering a middleware means telling the framework *which requests* it should wrap. Up to Laravel 10 that list lived in `app/Http/Kernel.php` as the `$middleware`, `$middlewareGroups` and `$middlewareAliases` properties. Laravel 11 introduced the slim application skeleton, and Laravel 13 keeps it: the app contains **no HTTP kernel class of its own**. The framework's `Illuminate\Foundation\Http\Kernel` still exists and still runs the request, but you configure it from `bootstrap/app.php`: ```php return Application::configure(basePath: dirname(__DIR__)) ->withRouting(web: __DIR__.'/../routes/web.php', health: '/up') ->withMiddleware(function (Middleware $middleware): void { $middleware->append(SetBookingCurrency::class); }) ->withExceptions(function (Exceptions $exceptions): void { // })->create(); ``` The `$middleware` argument is an `Illuminate\Foundation\Configuration\Middleware` object. It collects your instructions and, when the HTTP kernel is resolved, writes the final global stack, groups, aliases and priority list into it. ## The global stack: append() and prepend() The **global stack** is the list of middleware that wraps *every* HTTP request the application handles. Laravel ships a default global stack (including proxy trust, CORS, maintenance mode, post-size checks and input normalisation), and two methods add yours to it: - `$middleware->append(Foo::class)` adds `Foo` **after** the framework's default global entries. - `$middleware->prepend(Foo::class)` adds `Foo` **before** them, so it is the outermost layer you control. - Both accept a single class string or an array, and both de-duplicate: registering the same class twice leaves one entry. - Several `append()` calls keep their call order, while each later `prepend()` call lands **in front of** the earlier prepended entries, so the last `prepend()` becomes the outermost layer. For a travel-agency site, a middleware that tags every response with a correlation header, or one that reads the visitor's preferred currency cookie for every page and API call, is a fair global candidate. ## Route-level assignment Most middleware is not global. A check such as "the signed-in user is a verified travel agent" only makes sense on the agent back-office routes, so it is attached where those routes are defined: ```php Route::get('/agent/bookings', [AgentBookingController::class, 'index']) ->middleware(EnsureAgentIsVerified::class); ``` `->middleware()` accepts a class name, an array of them, an **alias** registered with `$middleware->alias()`, a group name such as `web`, and an alias with parameters such as `role:agent`. Route groups and controller-level attributes can carry middleware too; each of those is its own topic. Every route file you pass to `withRouting()` is also wrapped in a **group**: `routes/web.php` gets the `web` group and, when you opt into an API with `install:api`, `routes/api.php` gets the `api` group plus the `/api` prefix. To add a middleware to all web routes but not to API routes, you edit the group (`$middleware->web(append: ...)`), not the global stack. ## What global placement changes, mechanically | Property | Global (`append` / `prepend`) | Route (`->middleware()`) | |---|---|---| | When it runs | Before the router matches a route | After matching, around the controller | | Requests covered | Every HTTP request, including 404s and the `/up` health route | Only routes it is attached to | | Removable per route with `withoutMiddleware()` | No | Yes | | Reordered by the `priority()` list | No, runs in stack order | Yes | | Can read `$request->route()` before calling `$next` | No, no route is matched yet | Yes | Two consequences follow in practice: 1. A global middleware that needs the matched route, a bound model or the session is in the wrong place: the session and route bindings are started by the `web` group, which runs later. 2. A global middleware cannot be switched off for one endpoint with `withoutMiddleware()`; if one route must skip it, either move it into a group or make it skip itself on a path check. ## Choosing the registration point A useful rule when an interviewer asks "where does this check belong": - **Every request, independent of the route** (request IDs, trusted proxies, maintenance mode): global. - **Every browser page but not the API** (locale from the session, shared view data): the `web` group. - **A family of routes** (agent back office, admin area): a route group or a named middleware group. - **One endpoint**: `->middleware()` on that route. Starting narrow and widening later is cheap; a global middleware that later needs exceptions tends to collect path checks that nobody can audit.

  • Why does a global middleware in Laravel get null from $request->route() before it calls $next?
    The global stack runs in the HTTP kernel's pipeline before the request is handed to the router, so at that point no route has been matched and `$request->route()` returns null; only after `$next` returns has the router set the route. Anything that needs route parameters, a bound model or the route name must be route middleware or group middleware, which the router runs after matching.
  • In Laravel 13, how do you add a middleware to every web page but not to the API routes?
    Append it to the `web` group in `bootstrap/app.php` with `$middleware->web(append: [SetLocale::class])`. `routes/web.php` is wrapped in the `web` group by `withRouting()`, while `routes/api.php` gets the separate `api` group, so the API never sees it.

saying these in an interview costs you the question

  • Says to add it to the $middleware array in app/Http/Kernel.php in a new app
  • Thinks append() makes the middleware run after the controller
  • Registers a session-dependent check globally and expects the session to be started
  • Believes withoutMiddleware() can switch off a global middleware for one route
  • Treats the web group and the global stack as the same list