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(Request $request, ...$params) 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,
];
}
An alias that is not registered, whose class is missing, or whose class has no handle() is rejected. Router::getRegisteredMiddleware() throws MiddlewareConfigurationException, which dispatch catches and answers with a MIDDLEWARE_CONFIGURATION error response.
Before 2.4.0 the router silently skipped the unknown alias and ran the action unprotected, so a typo in a middleware name disabled the check it named.
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');
nonce and isAdmin are the two the boilerplate ships. can above is an example of one you would write and register yourself.