Routers and URL Resolution

Magento tries routers in sort order until one matches; the standard router maps front name, controller and action to one class.

Syntaxpublic function match(\Magento\Framework\App\RequestInterface $request) // returns ActionInterface or null

A router is a class with one method, match(), which either returns an action object for the request or returns null so the next router can try. Magento holds the routers in an ordered list, so URL resolution is a chain of attempts rather than one table of patterns.

The storefront chain

OrderNameClassMatches
10robotsMagento\Robots\Controller\Router/robots.txt
20urlrewriteMagento\UrlRewrite\Controller\Routera row in url_rewrite, such as a product or category URL
30standardMagento\Framework\App\Router\Basefront name / controller / action
60cmsMagento\Cms\Controller\Routera CMS page identifier
100defaultMagento\Framework\App\Router\DefaultRoutereverything; hands over to the no-route action

In adminhtml the chain is two entries: Magento\Backend\App\Router at 10 and the default router at 100.

The standard router

It splits the path into front name, controller path and action, defaulting the last two to index. The front name comes from etc/frontend/routes.xml; the class is Vendor\Module\Controller\ControllerPath\Action. So /blog/post/view/id/3 resolves to Vendor\Blog\Controller\Post\View with id as a request parameter. Admin URLs put the admin front name first and the class lives under Controller/Adminhtml/.

What trips people up

Two modules may claim one front name; the standard router tries them in declared order and takes the first with a matching class. A controller folder named Adminhtml is deliberately unreachable from the standard router, so a storefront route cannot borrow an admin controller.

Example

Example · php
<?php
// app/code/Vendor/ShortLink/Controller/Router.php
declare(strict_types=1);

namespace Vendor\ShortLink\Controller;

use Magento\Framework\App\Action\Forward;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\ActionInterface;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\RouterInterface;

class Router implements RouterInterface
{
    public function __construct(
        private readonly ActionFactory $actionFactory
    ) {
    }

    public function match(RequestInterface $request): ?ActionInterface
    {
        if (!$request instanceof HttpRequest) {
            return null;
        }

        $path = trim((string)$request->getPathInfo(), '/');
        if (!preg_match('#^t/(\d+)$#', $path, $matches)) {
            return null;
        }

        $request->setModuleName('catalog')
            ->setControllerName('product')
            ->setActionName('view')
            ->setParam('id', $matches[1]);

        return $this->actionFactory->create(Forward::class);
    }
}

When to use it

  • Serve a short link such as /t/123 without inventing a new front name.
  • Work out why a pretty URL reaches a product page and never gets to your controller.
  • Decide between a URL rewrite row and a custom router for a set of legacy URLs.
  • Find which class answers a given admin URL.

More examples

Joining the router list

Array arguments merge rather than replace, so you add one item instead of restating the whole list. 25 places the router after urlrewrite and before standard, which means a real product URL still wins over your pattern. The file sits under etc/frontend, so the router is only in the storefront chain and never runs for admin or REST. Apply it with bin/magento cache:flush; in production mode run bin/magento setup:di:compile first. Setting disable to true on an existing item, such as cms, removes a core router from the chain.

Example · xml
<?xml version="1.0"?>
<!-- app/code/Vendor/ShortLink/etc/frontend/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\RouterList">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="vendor_shortlink" xsi:type="array">
                    <item name="class" xsi:type="string">Vendor\ShortLink\Controller\Router</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">25</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

A front name for the standard router

router id="standard" is the storefront router; the route's frontName is the URL segment and the id is the internal route name that appears in the full action name (blog_post_view). With this file in place, /blog resolves to Vendor\Blog\Controller\Index\Index because both missing segments default to index. routes.xml is read into the configuration cache, so run bin/magento cache:flush after adding or editing it, otherwise the URL keeps returning the 404 page.

Example · xml
<?xml version="1.0"?>
<!-- app/code/Vendor/Blog/etc/frontend/routes.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="blog" frontName="blog">
            <module name="Vendor_Blog"/>
        </route>
    </router>
</config>

The same file for the admin

The admin router expects four segments, admin front name first, so this route answers <admin>/vendor_blog/post/index and loads Vendor\Blog\Controller\Adminhtml\Post\Index. The admin front name is not fixed: it comes from backend/frontName in app/etc/env.php and defaults to admin. before and after order the modules that answer one route, which is how core modules insert their controllers ahead of Magento_Backend's fallbacks. Apply with bin/magento cache:flush.

Example · xml
<?xml version="1.0"?>
<!-- app/code/Vendor/Blog/etc/adminhtml/routes.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="vendor_blog" frontName="vendor_blog">
            <module name="Vendor_Blog" before="Magento_Backend"/>
        </route>
    </router>
</config>

Discussion

  • Be the first to comment on this lesson.