As designed String parameter routes don't seem to match properly if the parameter contains a dot.

Affected version

Jake B.

Well-known member
I've got a route /test/sub/:id
Route prefix: test
Sub-name: sub
Route format: sub/:str<sub_id>

Anything with a sub_id containing a ., for example app.test gives me:

The requested page could not be found. (Code: invalid_action, controller: Test:Sub, action: AppTest)


XenForo developer
Staff member
The short answer is that :str match is alphanumeric and underscore/dash only. A dot is used as part of int matching and resolves ambiguity in routes that can support both string and int style matching.

There isn't really a match that is like string plus dot. :any is the closest, though that will basically consume up to the end normally, so you may have to get a bit more creative if you want actions. It's not really designed for this usage.

Jake B.

Well-known member
Managed to get it working pretty easily this morning:


namespace Test\XF\Mvc;

use XF\Mvc\RouteBuiltLink;

class Router extends XFCP_Router
    public function generateMatchRegexInner($format, $wrapper = '#')
        $matchRegex = parent::generateMatchRegexInner($format, $wrapper);

        $matchRegex = preg_replace_callback(
            function ($match) {
                $mainMatch = '(?:(?P<' . $match[2] . '>[a-zA-Z0-9_-]+)/)';
                return $match[1] ? $mainMatch : "{$mainMatch}?";

        return $matchRegex;

    protected function buildRouteUrl($prefix, array $route, $action, $data = null, array &$parameters = [])
        $url = parent::buildRouteUrl($prefix, $route, $action, $data, $parameters);

        if (!empty($route['build_callback']) && is_string($url) || $url instanceof RouteBuiltLink) {
            return $url;

        $url = preg_replace_callback(
            function($match) use ($data, &$parameters) {
                $inParams = !empty($match[1]);
                $stringKey = $match[2];
                $trailingSlash = $match[3];

                $search = $inParams ? $parameters : $data;

                if ($search && isset($search[$stringKey])) {
                    $key = strval($search[$stringKey]);

                    if ($inParams) {

                    if (strlen($key)) {
                        return $key . $trailingSlash;

                return '';

        return $url;