How to Create Custom Middleware in Laravel: A Modern Guide

How to Create Custom Middleware in Laravel A Modern Guide

Written by

in

A Laravel route without the right protection can feel like an office with every door unlocked. Anyone may reach pages, actions, or resources that should remain restricted. That is why I use middleware as a smart checkpoint between an incoming request and the application logic behind it.

In this guide, I’ll show you how to create custom middleware in Laravel to protect admin routes, verify user roles, control subscriptions, and filter requests without cluttering controllers. I’ll also explain the modern setup for Laravel 11, 12, and 13, along with the key difference for older Laravel versions.

What Does Custom Middleware Do in Laravel?

Middleware acts as a filter in Laravel’s HTTP request lifecycle. When a request enters the application, middleware can inspect it before allowing it to continue to a route or controller.

Laravel includes built-in middleware for common requirements such as authentication and CSRF protection. Custom middleware extends that idea to application-specific requirements.

For example, a US-based SaaS platform might use middleware to restrict account-management pages to administrators, verify an active subscription before displaying premium features, check user roles, log requests, or enforce additional API requirements.

The major advantage is separation of concerns. Instead of repeating permission checks inside several controllers, I can define the rule once and reuse it. This same approach is useful when troubleshooting How to Fix CORS Error in React and Node.js, where keeping frontend and backend configuration rules organized makes it easier to identify and resolve cross-origin issues.

How Do I Generate Custom Middleware in Laravel? 

Laravel’s Artisan command-line tool creates the basic middleware class for me. From the project’s root directory, I run:

php artisan make:middleware EnsureUserIsAdmin

Laravel generates:

app/Http/Middleware/EnsureUserIsAdmin.php

Using a descriptive name such as EnsureUserIsAdmin also makes the purpose immediately understandable when another developer reviews the project.

How Do I Add Custom Logic to the handle() Method?

How Do I Add Custom Logic to the handle() Method

Next, I open the generated class and add my filtering logic to its handle() method:

<?php

namespace App\Http\Middleware;

use Closure;

use Illuminate\Http\Request;

use Symfony\Component\HttpFoundation\Response;

class EnsureUserIsAdmin

{

    public function handle(Request $request, Closure $next): Response

    {

        if (!$request->user() || !$request->user()->is_admin) {

            return redirect()

                ->route(‘home’)

                ->with(‘error’, ‘Unauthorized access.’);

        }

        return $next($request);

    }

}

The $request->user() check confirms that an authenticated user exists, while is_admin represents an application-specific field used to determine administrative access.

When the condition passes, $next($request) sends the request deeper into Laravel’s application pipeline.

Should I Redirect the User or Return 403?

The right response depends on the application.

For a traditional website, I may redirect the visitor to a safe page and display an error. If an authenticated user is attempting to access something they do not have permission to use, I often prefer:

abort(403, ‘Unauthorized access.’);

For an API, a JSON response is usually more appropriate:

return response()->json([

    ‘message’ => ‘You do not have permission to access this resource.’

], 403);

Choosing the appropriate response makes the behavior clearer for users, front-end applications, and API consumers.

How Do I Register Middleware in Laravel 11, 12, and 13?

This is where many older tutorials can cause confusion. Modern Laravel applications configure middleware through bootstrap/app.php rather than the legacy HTTP kernel.

After learning how to create custom middleware in Laravel, I register an alias so I can easily reuse the middleware across routes.

A modern bootstrap/app.php configuration can look like this:

<?php

use App\Http\Middleware\EnsureUserIsAdmin;

use Illuminate\Foundation\Application;

use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))

    ->withRouting(

        web: __DIR__.’/../routes/web.php’,

        commands: __DIR__.’/../routes/console.php’,

        health: ‘/up’,

    )

    ->withMiddleware(function (Middleware $middleware) {

        $middleware->alias([

            ‘admin’ => EnsureUserIsAdmin::class,

        ]);

    })

    ->create();

The admin alias now gives me a short, readable way to assign EnsureUserIsAdmin to routes.

What About Laravel 10 and Earlier?

What About Laravel 10 and Earlier

Laravel 10 and earlier applications commonly register route middleware in:

app/Http/Kernel.php

Always verify your Laravel version before changing middleware configuration. Copying an old Kernel.php tutorial into a modern Laravel project is a common source of unnecessary errors.

How Do I Protect Laravel Routes With Custom Middleware?

Once the alias exists, I can protect a single route in routes/web.php:

use App\Http\Controllers\AdminController;

Route::get(‘/admin/dashboard’, [AdminController::class, ‘index’])

    ->middleware(‘admin’);

For several admin routes, a middleware group keeps the routing file cleaner:

Route::middleware([‘auth’, ‘admin’])->group(function () {

    Route::get(‘/admin/settings’, [AdminController::class, ‘settings’]);

    Route::get(‘/admin/users’, [AdminController::class, ‘users’]);

});

Adding auth before admin allows Laravel’s authentication middleware to handle guests before my custom authorization check runs.

Can I Use the Middleware Class Without an Alias?

Yes. I can assign the class directly:

use App\Http\Middleware\EnsureUserIsAdmin;

Route::get(‘/admin/dashboard’, [AdminController::class, ‘index’])

    ->middleware(EnsureUserIsAdmin::class);

I usually prefer aliases when middleware appears repeatedly because they make route definitions shorter.

How Do Global and Web Middleware Registration Work?

Laravel also allows middleware to run globally. Inside the middleware configuration, I can append a class with:

$middleware->append(EnsureUserIsAdmin::class);

That makes it run on every HTTP request, so I would not normally make an admin-only authorization check global.

Middleware can also be added to Laravel’s web middleware group:

$middleware->web(append: [

    EnsureUserIsAdmin::class,

]);

I use these approaches only when a middleware rule genuinely applies to every request or every route within that middleware stack. Route-level aliases are safer for narrowly targeted authorization rules.

How Do I Pass Parameters to Laravel Middleware?

Parameterized middleware lets one class support several roles.

For example:

public function handle(

    Request $request,

    Closure $next,

    string $role

): Response {

    if (!$request->user() || $request->user()->role !== $role) {

        abort(403);

    }

    return $next($request);

}

I can then apply a role through an alias:

Route::get(‘/editor’, [EditorController::class, ‘index’])

    ->middleware(‘role:editor’);

This approach can be cleaner than creating separate middleware classes for administrators, editors, managers, and other roles.

How Do I Test Custom Middleware?

How Do I Test Custom Middleware

I test both authorized and unauthorized scenarios. Laravel feature tests make that straightforward:

$response = $this->actingAs($regularUser)

    ->get(‘/admin/dashboard’);

$response->assertForbidden();

I would also test an administrator and verify that the protected route succeeds. Testing both paths prevents future application changes from silently breaking access controls.

Why Is My Laravel Middleware Not Working?

If Laravel does not recognize my middleware alias, I first confirm that the alias in bootstrap/app.php exactly matches the route definition.

I can also clear cached application data:

php artisan optimize:clear

If $request->user() returns null, I verify that authentication runs before the custom middleware by using [‘auth’, ‘admin’].

For a Target class does not exist error, I check the namespace, filename, class name, imports, and alias. After changing namespaces or classes, regenerating Composer’s autoloader may help:

composer dump-autoload

These checks solve many of the common middleware configuration problems I encounter.

What Are the Best Practices for Laravel Custom Middleware?

I keep middleware focused on request filtering and avoid placing large business workflows inside the handle() method. Complex authorization may belong in Laravel policies, while broader business operations generally belong in dedicated application or service layers.

Clear names such as EnsureUserIsAdmin, CheckSubscription, and VerifyAccountStatus also make larger applications easier to understand.

Most importantly, I check the Laravel version before following registration instructions. The move from app/Http/Kernel.php to configuration in bootstrap/app.php makes version-aware guidance essential.

Frequently Asked Questions (FAQs)

1. Where is custom middleware stored in Laravel?

Laravel normally creates custom middleware classes inside the app/Http/Middleware directory.

2. Can Laravel routes use multiple middleware?

Yes. A route or route group can use multiple middleware, such as auth followed by custom role or permission middleware.

3. Do I have to create an alias for custom middleware?

No. Laravel allows you to assign the middleware class directly to a route. Aliases are mainly useful for shorter, reusable route definitions.

4. How to create custom middleware in Laravel for role-based access?

Generate a middleware class with Artisan, check the authenticated user’s role inside handle(), optionally create an alias in bootstrap/app.php, and attach it to the routes or route groups that require that role.

Make Middleware Work for You, Not Against You

I find custom middleware most valuable when I treat it as a focused request filter rather than a place to store every authorization or business rule. A well-designed middleware class can protect routes, verify roles, enforce subscriptions, and keep repetitive request checks out of controllers.

For current Laravel projects, the greatest detail to remember is where registration happens. Laravel 11, 12, and 13 use bootstrap/app.php, while older projects may still rely on app/Http/Kernel.php. Once I combine the correct registration method with clear aliases, targeted route groups, parameters, and feature tests, middleware becomes a simple and maintainable part of the application architecture.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *