Mastering Custom Middleware in Laravel
Building Robust and Scalable Middleware for Complex Applications

Introduction
Middleware in Laravel is powerful. It acts as a layer that processes HTTP requests entering your application before they hit your controllers. Most developers use middleware for things like authentication and logging, but the true power of middleware goes beyond these basics.
In this article, we'll cover:
- Building complex middleware with dependencies
- Middleware groups and dynamic middleware stacking
- Passing parameters to middleware
- Conditional middleware execution
- Advanced use cases for building scalable applications
Understanding Middleware in Laravel
Middleware provides a convenient mechanism for filtering HTTP requests. Laravel ships with several middleware by default, such as auth
, csrf
, and throttle
.
The simplest middleware looks like this:
namespace App\Http\Middleware; use Closure; class EnsureUserIsActive { public function handle($request, Closure $next) { if (! $request->user() || ! $request->user()->is_active) { return redirect('inactive'); } return $next($request); } }
This middleware checks if the user is active before allowing the request to proceed.
Using Middleware Groups
Instead of applying middleware to each route individually, you can group middleware.
Example
protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\EnsureUserIsActive::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ];
Middleware groups are especially useful for applying global rules to different parts of your application (web
, api
, admin
).
Passing Parameters to Middleware
Middleware can receive custom parameters, making it even more powerful.
Example: Role-Based Access Control
Route::get('/admin', function () { // })->middleware('role:admin');
Now define the middleware to accept a parameter:
namespace App\Http\Middleware; use Closure; class RoleMiddleware { public function handle($request, Closure $next, $role) { if (! $request->user() || ! $request->user()->hasRole($role)) { abort(403); } return $next($request); } }
Laravel will pass the admin
string to the $role
argument. You can pass multiple parameters by separating them with commas.
Middleware Dependency Injection
You can inject dependencies directly into your middleware’s constructor.
Example: Using a Service Class
namespace App\Http\Middleware; use Closure; use App\Services\AuditService; class LogUserActivity { protected $auditService; public function __construct(AuditService $auditService) { $this->auditService = $auditService; } public function handle($request, Closure $next) { $this->auditService->log($request->user()); return $next($request); } }
In AuditService
, you can handle complex logging or monitoring without cluttering your middleware logic.
Dynamic Middleware Stacking
Laravel allows you to stack middleware dynamically at runtime. This is useful for scenarios where you need to apply middleware conditionally or based on user input.
Example: Applying Middleware Dynamically
Route::get('/profile', function () { // })->middleware([EnsureUserIsActive::class, CheckUserSubscription::class]);
Or, apply middleware within a controller’s constructor:
class UserController extends Controller { public function __construct() { $this->middleware('auth'); $this->middleware('role:admin')->only(['create', 'store']); } }
This makes your middleware usage flexible and highly configurable.
Conditional Middleware Execution
What if you want to execute middleware only if certain conditions are met? This can be done using the ->when()
method or by manually checking conditions within the middleware itself.
Example: Applying Middleware Conditionally
Route::get('/dashboard', function () { // })->middleware('throttle:60,1')->when(request()->has('api'), function ($request) { return $request->header('Accept') === 'application/json'; });
This approach is useful for toggling middleware based on user roles, permissions, or other runtime conditions.
Middleware Priority
Sometimes, the order in which middleware is executed matters. Laravel allows you to define middleware priority using the $middlewarePriority
property.
protected $middlewarePriority = [ \App\Http\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\Authenticate::class, \App\Http\Middleware\EnsureUserIsActive::class, ];
Use this approach when certain middleware must always run before or after others, regardless of how they are registered.
Middleware Tips & Best Practices
- Keep Middleware Lightweight: Avoid putting complex logic directly in middleware. Offload to services where possible.
- Use Dependency Injection: Inject services into middleware instead of resolving them manually.
- Organize Middleware Groups: Group related middleware to maintain readability and maintainability.
- Use Route-Specific Middleware: Apply middleware only where it’s needed for better performance.
- Leverage Middleware Priority: Control execution order where necessary for complex workflows.
Summary
Custom middleware in Laravel is not just for authentication or basic filters. By using advanced techniques like dependency injection, contextual middleware application, and dynamic middleware stacking, you can build powerful, flexible, and maintainable systems.
Middleware is a key part of building scalable Laravel applications, so understanding these techniques will help you architect better solutions.