Skip to main content

Middleware

Introduction​

Middleware runs before the controller and can stop the request. Use it for the checks that repeat across routes — authentication, capability checks, nonce verification.

Writing middleware​

A middleware is a class in backend/app/HTTP/Middleware with a handle() method. Return true to let the request through, or a Response to stop it:

namespace YourPlugin\HTTP\Middleware;

if (!defined('ABSPATH')) {
exit;
}

use YourPlugin\Deps\BitApps\WPKit\Http\Response;
use YourPlugin\Deps\BitApps\WPKit\Utils\Capabilities;

final class AdminCheckerMiddleware
{
public function handle()
{
if (!Capabilities::check('manage_options')) {
return Response::error('Access Denied: Only administrators are allowed to make this request')
->httpStatus(411);
}

return true;
}
}

Anything other than true is treated as the response and sent immediately — the controller never runs.

handle() can type-hint the request, which the router injects the same way it does for controllers:

final class NonceCheckerMiddleware
{
public function handle(Request $request)
{
if (!$request->has('_ajax_nonce')
|| !wp_verify_nonce(sanitize_key($request->_ajax_nonce), Config::withPrefix('nonce'))
) {
return Response::error('Invalid nonce token')->httpStatus(411);
}

return true;
}
}

Registering middleware​

Routes refer to middleware by short name. Map the names to classes in Plugin::middlewares():

public function middlewares()
{
return [
'nonce' => NonceCheckerMiddleware::class,
'isAdmin' => AdminCheckerMiddleware::class,
];
}
warning

A route that names an unregistered middleware is not an error — the router finds nothing to run and the request proceeds. A route asking for isAdmin against an empty registry answers as though it had no protection at all, so keep the registry in sync when you add middleware.

Attaching middleware​

Attach by name to a single route or a group:

Route::post('flows/save', [FlowController::class, 'store'])->middleware('nonce');

Route::group(
function (): void {
// every route in here runs both
}
)->middleware('nonce', 'isAdmin');

Group and route middleware merge, and run in the order they were added — the group's first.

Middleware parameters​

Pass parameters after a colon; multiple parameters are comma-separated. They arrive as arguments to handle().

Given a middleware of your own registered as can:

public function handle($capability, $fallback = null)
{
return Capabilities::check($capability) ?: Response::error('Access Denied')->httpStatus(411);
}

a route selects the capability at the call site:

Route::get('flows', [FlowController::class, 'index'])->middleware('can:manage_options,edit_posts');
note

nonce and isAdmin are the two the boilerplate ships. can above is an example of one you would write and register yourself.