Skip to content
TubePress — free, self-hosted & actively maintained
Developer reference

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

MethodUse
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.

Avoid clashing with a page slug. A plugin route such as /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.

Try the live demo → Download TubePress →