Route::middleware
Attaches middleware to a route or to every route in a group, in the order listed.
middleware(array|string|null $middleware): Illuminate\Routing\RouteRegistrarParameters
| Parameter | Type | Description |
|---|---|---|
$$middleware | array|string|null | One middleware or a list of them, given as class names or as aliases registered in bootstrap/app.php. Parameters follow a colon: 'throttle:60,1', 'can:update,post'. |
Return value
Illuminate\Routing\RouteRegistrarA RouteRegistrar, so the call chains into group() or into further attribute methods such as prefix() and name(). Called on a Route instance instead, it returns that route ($this), or the route's current middleware array when given no argument.
Errors
An unregistered middleware alias raises an exception when the route is matched, not when it is registered.
Route::middleware is how a route says what has to happen before and after its action runs: authenticate, authorise, throttle, verify a signature, start a transaction.
Order matters
The docs are explicit: middleware are executed in the order they are listed in the array. Group middleware run before middleware attached to the individual route, and both run outward-in on the way down and inward-out on the way back. So ['auth', 'throttle:60,1'] authenticates first and only then counts the request against the limit — which means unauthenticated traffic is never throttled. Reverse the pair if the limit is meant to protect the login attempt itself.
What trips people up
- Framework middleware can be reordered. Laravel keeps a middleware priority list; some built-in middleware are sorted by it regardless of the order you wrote.
- Aliases must exist. A typo in an alias is not caught at registration — it blows up the first time the route is hit.
- Removing is separate from adding. Use
withoutMiddleware(), orwithoutMiddlewareFor()on a resource, to subtract from what a group applied. - Parameters are colon-separated and comma-delimited, and
route:list -vis the fastest way to see what actually ended up attached.
Example
<?php
use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route;
// On a single route.
Route::get('/profile', [UserController::class, 'show'])
->middleware('auth')
->name('profile');
// On a group: every route inside inherits both, in this order.
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/photos', [PhotoController::class, 'index'])->name('photos.index');
// Subtract from what the group applied.
Route::get('/photos/public', [PhotoController::class, 'publicIndex'])
->withoutMiddleware('verified')
->name('photos.public');
});
echo route('photos.index');
// http://example.com/photosWhen to use it
- Put an entire admin section behind auth and a permission check.
- Rate limit a login or token endpoint with throttle:5,1 before authentication runs.
- Authenticate API routes with auth:sanctum while leaving index public.
- Strip verified from one route in an otherwise verified group with withoutMiddleware.
Frequently asked questions
In what order does Laravel run route middleware?
In the order listed, with a group's middleware running before those attached to the individual route. Laravel's middleware priority list can reorder certain framework middleware regardless of how you wrote them.
How do I pass an argument to a middleware from a route?
Append it after a colon, comma-separating multiple values: 'throttle:60,1', 'can:update,post', 'auth:sanctum'. The values arrive as extra arguments to the middleware's handle method.
How do I remove middleware that a group applied?
Chain ->withoutMiddleware('verified') on the individual route, or ->withoutMiddlewareFor('index', ['auth']) on a resource registration. It can only remove middleware that was added by an enclosing group.
More examples
Order changes behaviour
Both lines are correct for their purpose, and the difference is only the ordering. On /login the limiter must run before authentication or failed attempts would be free; on the API the reverse is wanted, so that the per-minute budget applies to a logged-in identity rather than to anonymous callers.
<?php
use Illuminate\Support\Facades\Route;
// Throttle FIRST: brute-force attempts are counted even when login fails.
Route::post('/login', [LoginController::class, 'store'])
->middleware(['throttle:5,1', 'guest'])
->name('login');
// Auth FIRST: only authenticated users consume the API budget,
// so anonymous traffic is rejected before it touches the limiter.
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
Route::get('/photos', [PhotoController::class, 'index'])->name('api.photos.index');
});
echo route('login');
echo PHP_EOL;
echo route('api.photos.index');
// http://example.com/login
// http://example.com/api/photosMiddleware with parameters, and per-action control on a resource
can:update,post resolves the {post} route parameter and runs the policy against it, so authorization is declared on the route rather than repeated in the controller. The resource block shows the subtractive form: the group applies three middleware, and withoutMiddlewareFor peels specific ones off individual generated actions.
<?php
use App\Http\Controllers\UserController;
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Support\Facades\Route;
// Parameters follow a colon and are comma separated.
Route::get('/posts/{post}/edit', [PostController::class, 'edit'])
->middleware(['auth', 'can:update,post'])
->name('posts.edit');
// A class name works anywhere an alias does.
Route::get('/internal', InternalController::class)
->middleware(EnsureTokenIsValid::class);
// Resource controllers take the same treatment per action.
Route::middleware(['auth', 'verified', 'subscribed'])->group(function () {
Route::resource('users', UserController::class)
->withoutMiddlewareFor('index', ['auth', 'verified'])
->withoutMiddlewareFor('destroy', 'subscribed');
});
echo route('posts.edit', ['post' => 4]);
// http://example.com/posts/4/edit
Discussion