Routing
The TubePress Router API: how front-end, admin and API routes are registered, matched and dispatched, including route parameters and HTTP methods.
TubePress uses a tiny, fast router built around the static Router class. Controllers register their routes in a ::routes() method, and public/index.php calls those methods before dispatching the request. This page covers everything you need to add routes from a plugin or understand how requests are matched.
Registering routes
Register a route with Router::get() or Router::post(). The handler is any callable — a closure or a [Class, 'method'] pair.
// Closure handler
Router::get('/promo', function () {
ThemeRenderer::render('promo', ['deal' => 'Summer']);
});
// Controller handler
Router::post('/promo/claim', [PromoController::class, 'claim']);By convention each controller exposes a static routes() method that registers all of its routes, and that method is called once at boot:
class PromoController
{
public static function routes(): void
{
Router::get('/promo', [self::class, 'show']);
Router::post('/promo/claim', [self::class, 'claim']);
}
}Route parameters
Use {name} placeholders in the path; each matches a single URL segment (everything except /). Read the value with Router::param().
Router::get('/promo/{code}', function () {
$code = Router::param('code'); // required
$ref = Router::param('ref', 'direct'); // with a default
// …
});Internally a pattern such as /promo/{code} is compiled to the regex (?P<code>[^/]+), so parameters never span slashes. Exact routes are matched first, then parameterised ones in registration order.
Sending a response
| Method | Use |
|---|---|
ThemeRenderer::render($tpl, $data) | Render a front-end page through the active theme. |
Router::render($tpl, $data) | Render an admin template (templates/{$tpl}.php). |
Router::json($data, $code = 200) | Send a JSON response and exit. |
Router::redirect($url) | Redirect (and exit). Front-end URLs are auto-prefixed with the active language/type. |
Route order & the catch-all
Static pages are served by a /{slug} catch-all, so the page controller is registered last in public/index.php — after every other front, admin and API route. If you register routes from a plugin during boot, they are added before the catch-all and will match first.
/deals will shadow a static page at /deals. Namespace plugin routes (for example /promo/deals) to be safe.CSRF protection
Every state-changing POST should be CSRF-protected. The router provides the helpers:
// In your form template
<?= Router::csrfField() ?> // hidden input
// In your POST handler
if (!Router::verifyCsrf()) {
Router::json(['error' => 'Invalid token'], 403);
}verifyCsrf() accepts the token from the csrf_token POST field or the X-CSRF-Token header, and compares it with hash_equals().
Localised & typed URLs
For multilingual sites the router transparently handles a language prefix (for example /fr/…) and translates known route slugs, so /categorie/brunette resolves to the category route. Admin and install routes are exempt — register a path with Router::addNoPrefixPath() if you need to opt a custom path out of prefixing. See Languages & translation for the full behaviour. Helpers Router::langPrefix(), Router::typePrefix() and Router::url() let you build correct links in any locale.
Next steps
Still stuck?
Open a ticket from your dashboard and our team will help you out.