Platform Infrastructure Hooks
The eight hooks under the request: outgoing calls, accepted addresses, routing, template variables, API addresses and product types.
Overview
These hooks sit beneath a request: before an address resolves, before a template is shown, before a call leaves for the outside. All are hot paths, running on every request.
Two deserve care. The pre-match stands ahead of the real pages of the install, so an unproven claim shadows them. And template variables is not passed by link: the array you return stands in for all of them.
Reference
Changing an outgoing request
Runs before the system sends a request outward. Every outgoing call passes here: server modules, registrars, payment gateways.
Hook::add('filter:http.request', 10, function (&$options) {
// Touch only your own address: this hook sees EVERY outgoing call.
if (!str_contains($options['url'] ?? '', 'api.acme.test')) return;
$options['headers'][] = 'X-Acme-Tenant: ' . Acme::tenant();
});Widening the accepted addresses
Runs while the address of an incoming request is checked. The list arrives holding the address the licence is locked to; you add yours.
Hook::add('filter:http.trusted_hosts', 10, function (&$hosts, &$context) {
// Add, do not reset: the locked address must stay in the list.
$hosts[] = 'panel.acme.test';
});Registering an address of your own
Runs while the address table is built. This is the right way to open a page of your own: register the address rather than grabbing it in the match hooks.
Hook::add('register:routes', 10, function ($router) {
// Registration happens through the call, not the return.
$router->add('acme-status', 'acme/status', 'AcmeStatusController');
});Claiming an address ahead of the patterns
Runs as an address starts to resolve, ahead of the registered patterns. Answering here means stepping in front of the real pages of the install.
null unless your own record stands behind it. For a permanent page the right place is the address registration, not this.Hook::add('filter:routing.prematch', 10, function ($url, $routes) {
// Claim with proof: return null unless you hold a record for it.
if (!Acme::ownsSlug($url)) return null;
return ['key' => 'acme-page', 'controller' => 'AcmePageController', 'params' => [$url]];
});Catching an address nothing matched
Runs when none of the registered patterns held. Unlike the pre-match, claiming here is safe: there is no real page left to shadow.
Hook::add('filter:routing.match', 10, function ($url, $routes) {
// No pattern held this address, so claiming it is safe.
$page = Acme::findVanity($url);
if (!$page) return null;
return ['key' => 'acme-vanity', 'controller' => 'AcmeVanityController', 'params' => [$page]];
});Changing the variables a template receives
Runs before a template is shown. This is how you carry one value onto every page.
Hook::add('filter:template.variables', 10, function ($template_path, $data) {
// ADD to what arrived and return all of it: this replaces, it does not merge.
$data['acme_banner'] = Acme::banner();
return $data;
});Opening API addresses of your own
Runs while the API address table is built. This is how a module opens its own endpoints without touching the core.
admin, client or module. The hook fires separately for all three: add without checking and your endpoint lands in every table.Hook::add('filter:api.routes', 10, function (&$routes, &$audience) {
// The hook fires for all three tables: check the branch.
if ($audience !== 'admin') return;
$routes[] = ['GET', 'acme/status', 'Module:Addons/Acme', 'status'];
});Introducing a new product type
Runs while the product type list is built. Add your own type to the core list here.
Hook::add('register:product_types', 10, function () {
return ['acme-vps' => [
'title' => 'Acme VPS',
'description' => 'A virtual server on Acme',
'icon' => 'bi bi-hdd-rack fs-5',
]];
});Pitfalls
This filter is not passed by link. The array you return stands in for every variable. Return one of your own without building on what arrived and the page loses its data, leaving a blank screen. The fix: add to the incoming array and return all of it.
The pre-match sits ahead of the registered patterns. Answering "this might be mine" makes the real pages of the install unreachable. Match only where your own record stands behind the address; for a permanent page the right place is the address registration.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.