In EE, if there was a template /foo/bar.html, one could access the contents of that template by going to either example.com/foo/bar or example.com/foo/bar/more/segments/here. The additional URL segments didn't prevent EE from loading the most-accurately-matching template.

Moving over to Craft, bar is now an entry's slug. The section for that entry is properly set up with the "Entry URI Format" and "Template" pointing to foo/{slug} and foo/_entry.html, respectively. Is there a way to get the same "most-accurate" template-matching behavior in Craft? Perhaps with routes? I would want the {{ entry }} variable to be automatically available for either URL.

1 Answer 1


Yes and no. Yes it is possible to include as many additional segments to selected routes and it's even possible to achieve what you want but it might become resource heavy and you would need to handle the entire routing on your own.


If it's for a channel with a set URI format like foo/{slug} you can implement a route like

    function(RegisterUrlRulesEvent $event) {
        $event->rules[] = [
            'pattern'  => 'foo/<uri:\S+>/<additionals:\S+>',
            'route'    => 'your-module/your-controller/route',
            'defaults' => [
                'additionals' => ''

So all your urls like foo/{slug} as well as foo/{slug}/bar/whatever will be routed to your controller YourController::actionRoute with the parameters

public function actionRoute(string $uri, string $additionals = null)
    $element = Craft::$app->getElements()->getElementByUri($uri);
    $template = (new Query())->select('template')
        ->where(['=', 'siteId', $element->site->id])
        ->andWhere(['=', 'sectionId', $element->sectionId])

    return $this->renderTemplate($template, ['entry' => $element]);

Note keep in mind this will only work if the uri format is rather simple like foo/{slug} it won't really work for things like foo/{author}/{category.id}/{slug}/{title} you'll need the rule mentioned in "structures" for it


If it's for singles you can do it dynamically

try {
    $uris = (new Query())
        ->where(['=', 'siteId', Craft::$app->getSites()->getCurrentSite()->id])
        ->andWhere(['=', 'sectionId', [array of your section ids])
} catch (Exception $exception) {
    $uris = [];

if (empty($uris) === false) {
    $event->rules[] = [
        'pattern'  => '<uri:(' . implode('|', $uris) . ')>' . '/<additional:\S+>',
        'route'    => 'module/controller/action',
        'defaults' => [
            'additional' => ''


For structures it's not that easy... You would need to recursively remove one segment and try to search for a matching route if there is one, use it, otherwise remove the next one.

Attention pseudocode

$path = get your full path
while($element === null){
    $path = $path - lastSegment;
    $element = Craft::$app->getElements()->getElementByUri($path);
// attach the route `path` as you can see above

However I would not suggest the code above since it will search for a matching element for each segment for every request. I would highly recommend to include many conditions around it like Craft::$app->getRequest()->isSiteRequest() === true and $request->isAjaxRequest() === false and so on to reduce the amount of queries as much as possible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.