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?
answer
- no app/Http/Kernel.php in the app
- bootstrap/app.php, withMiddleware closure
- append() to the end, prepend() to the front
- ->middleware() on a route or group
- global runs before the route is matched
basics
~10 sGlobal 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 sSince 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
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
Recall the file and the closure: bootstrap/app.php, withMiddleware(), append() and prepend() for global, ->middleware() on a route for the rest.
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.
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.
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