Custom Module Routes
Understanding Magento's routing system, standard routes, and creating custom routers for unique URL schemas.
Routing System Overview
Magento Routing System
Two Parts of Routing
1. Standard Routing
- Three-chunk URL structure
- Format: frontname/controller/action
- Configured in routes.xml
- Example: catalog/product/view
Covered: This topic (1.10)
2. URL Rewrites
- SEO-friendly URLs
- Database-driven rewrites
- Maps custom URLs to standard routes
- Example: product-name.html
Covered: Topic 2.11
Standard Routing
Three-Chunk URL Structure
Magento uses a three-part URL structure for standard routing:
URL Format:
https://example.com/frontname/controller/action/param1/value1/param2/value2- frontname - Route identifier (e.g., catalog, customer, checkout)
- controller - Controller directory name (e.g., product, account, cart)
- action - Action class name (e.g., view, edit, add)
- parameters - Optional key/value pairs
Examples:
| URL | Front Name | Controller | Action | 
|---|---|---|---|
| catalog/product/view/id/123 | catalog | product | view | 
| customer/account/login | customer | account | login | 
| checkout/cart/add | checkout | cart | add | 
Declaring Routes
routes.xml Configuration
To handle requests, a module must declare a route in routes.xml:
File Locations:
- Frontend: etc/frontend/routes.xml
- Admin: etc/adminhtml/routes.xml
Example: Frontend Route
File: app/code/Bonlineco/Catalog/etc/frontend/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="catalog" frontName="catalog">
            <module name="Bonlineco_Catalog"/>
        </route>
    </router>
</config>Configuration Elements:
- <router id="standard">- Router type (standard for frontend/admin)
- <route id="catalog">- Unique route identifier
- frontName="catalog"- URL front name (first chunk)
- <module name="...">- Module that handles this route
Example: Admin Route
File: app/code/Bonlineco/Catalog/etc/adminhtml/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="bonlineco_catalog" frontName="bonlineco_catalog">
            <module name="Bonlineco_Catalog" before="Magento_Backend"/>
        </route>
    </router>
</config>Controller Mapping
Controller Structure
After declaring a route, create controllers that correspond to the URL structure:
URL to File Mapping:
URL: catalog/product/view
     ↓
Route: catalog (from routes.xml)
     ↓
Module: Bonlineco_Catalog
     ↓
Controller File: app/code/Bonlineco/Catalog/Controller/Product/View.php
     ↓
Class: Bonlineco\Catalog\Controller\Product\ViewDirectory Structure:
app/code/Bonlineco/Catalog/
├── Controller/
│   ├── Product/
│   │   ├── View.php        ← catalog/product/view
│   │   ├── Edit.php        ← catalog/product/edit
│   │   └── Delete.php      ← catalog/product/delete
│   ├── Category/
│   │   └── View.php        ← catalog/category/view
│   └── Index/
│       └── Index.php       ← catalog/index/index (or just catalog/)
└── etc/
    └── frontend/
        └── routes.xmlNaming Conventions:
- Controller directory = URL second chunk (capitalized)
- Action class = URL third chunk (capitalized)
- Default controller: Index
- Default action: Index
Custom URL Schemas
Beyond Standard Routing
Sometimes you need custom URL patterns that don't fit the three-chunk structure:
Examples of Custom URLs:
- product-123instead of- catalog/product/view/id/123
- product-configuration-ML-12-size-XL-color-RED
- blog/2024/10/my-post-title
- brand/nike/category/shoes
Front Controller Workflow
How Magento Processes Requests
Request Processing Flow:
- Request hits index.phporpub/index.php
- Bootstrap initializes - Application setup and configuration
- Front Controller runs - \Magento\Framework\App\FrontController
- Obtains router list - All routers registered via di.xml
- Loops through routers - In priority order
- Router processes request - First matching router handles it
- Returns ActionInterface - Action is executed
- Response sent - HTML, JSON, redirect, etc.
Front Controller Class:
\Magento\Framework\App\FrontControllerDefault Routers
Built-in Router Types
Magento includes several default routers that handle different types of requests:
| Router | Purpose | Priority | 
|---|---|---|
| Base Router | Handles admin and frontend standard routes | 20 | 
| Default Router | Fallback for unmatched routes (404) | 100 | 
| CMS Router | Handles CMS pages and blocks | 60 | 
| UrlRewrite Router | Processes URL rewrites from database | 40 | 
Creating a Custom Router
Custom Router Implementation
To create a custom router for handling non-standard URLs:
Step 1: Create Router Class
File: app/code/Bonlineco/CustomUrl/Controller/Router.php
<?php
namespace Bonlineco\CustomUrl\Controller;
use Magento\Framework\App\ActionFactory;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\RouterInterface;
class Router implements RouterInterface
{
    protected $actionFactory;
    
    public function __construct(
        ActionFactory $actionFactory
    ) {
        $this->actionFactory = $actionFactory;
    }
    
    /**
     * Match application action by request
     *
     * @param RequestInterface $request
     * @return \Magento\Framework\App\ActionInterface|null
     */
    public function match(RequestInterface $request)
    {
        $identifier = trim($request->getPathInfo(), '/');
        
        // Example: Handle URLs like "product-123"
        if (preg_match('/^product-(\d+)$/', $identifier, $matches)) {
            $productId = $matches[1];
            
            // Set request parameters
            $request->setModuleName('catalog')
                    ->setControllerName('product')
                    ->setActionName('view')
                    ->setParam('id', $productId);
            
            // Return action instance
            return $this->actionFactory->create(
                \Magento\Framework\App\Action\Forward::class
            );
        }
        
        // Return null if this router doesn't handle the URL
        return null;
    }
}Key Points:
- Implement RouterInterface
- Define match(RequestInterface $request)method
- Return ActionInterfaceinstance if matched
- Return nullif not matched (next router tries)
- Use ActionFactoryto create action instances
Registering Custom Router
di.xml Configuration
Register your custom router with the Front Controller:
File Location:
- Frontend: etc/frontend/di.xml
- Admin: etc/adminhtml/di.xml
- Both: etc/di.xml
Example Registration:
File: app/code/Bonlineco/CustomUrl/etc/frontend/di.xml
<?xml version="1.0"?>
<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="bonlineco_custom" xsi:type="array">
                    <item name="class" xsi:type="string">
                        Bonlineco\CustomUrl\Controller\Router
                    </item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">30</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>Configuration Options:
- name="bonlineco_custom"- Unique router identifier
- class- Your router class path
- disable- Enable/disable the router
- sortOrder- Priority (lower = earlier execution)
Advanced Router Examples
Real-World Custom Router
Example: CMS Router (Simplified)
Here's how Magento's CMS router works (simplified):
<?php
namespace Magento\Cms\Controller;
use Magento\Framework\App\RouterInterface;
use Magento\Framework\App\RequestInterface;
class Router implements RouterInterface
{
    protected $actionFactory;
    protected $pageFactory;
    
    public function __construct(
        \Magento\Framework\App\ActionFactory $actionFactory,
        \Magento\Cms\Model\PageFactory $pageFactory
    ) {
        $this->actionFactory = $actionFactory;
        $this->pageFactory = $pageFactory;
    }
    
    public function match(RequestInterface $request)
    {
        $identifier = trim($request->getPathInfo(), '/');
        
        // Try to load CMS page by URL key
        $page = $this->pageFactory->create();
        $pageId = $page->checkIdentifier($identifier, $request->getStoreId());
        
        if (!$pageId) {
            return null; // Not a CMS page, let another router try
        }
        
        // Set request to CMS page controller
        $request->setModuleName('cms')
                ->setControllerName('page')
                ->setActionName('view')
                ->setParam('page_id', $pageId);
        
        return $this->actionFactory->create(
            \Magento\Framework\App\Action\Forward::class
        );
    }
}Example: Blog Date-Based URLs
<?php
namespace Bonlineco\Blog\Controller;
class Router implements \Magento\Framework\App\RouterInterface
{
    public function match(\Magento\Framework\App\RequestInterface $request)
    {
        $identifier = trim($request->getPathInfo(), '/');
        
        // Match pattern: blog/2024/10/post-title
        $pattern = '/^blog\/(\d{4})\/(\d{2})\/([a-z0-9-]+)$/';
        if (preg_match($pattern, $identifier, $matches)) {
            $year = $matches[1];
            $month = $matches[2];
            $slug = $matches[3];
            
            $request->setModuleName('blog')
                    ->setControllerName('post')
                    ->setActionName('view')
                    ->setParam('year', $year)
                    ->setParam('month', $month)
                    ->setParam('slug', $slug);
            
            return $this->actionFactory->create(
                \Magento\Framework\App\Action\Forward::class
            );
        }
        
        return null;
    }
}Router Interface
RouterInterface Contract
All routers must implement \Magento\Framework\App\RouterInterface:
<?php
namespace Magento\Framework\App;
interface RouterInterface
{
    /**
     * Match application action by request
     *
     * @param RequestInterface $request
     * @return ActionInterface|null
     */
    public function match(RequestInterface $request);
}Match Method Requirements:
- Parameter: Receives RequestInterface $request
- Returns: ActionInterfaceif matched,nullotherwise
- Side effects: Can modify request (setModuleName, setParam, etc.)
- Performance: Should be fast to not slow down all requests
Action Types
Available Action Classes
When returning an action from your router, you can use:
| Action Type | Class | Purpose | 
|---|---|---|
| Forward | Magento\Framework\App\Action\Forward | Forward to another controller/action | 
| Redirect | Magento\Framework\App\Action\Redirect | HTTP redirect to another URL | 
| Custom | Your own ActionInterface implementation | Custom logic and response | 
Using Forward Action:
// Set the target controller
$request->setModuleName('catalog')
        ->setControllerName('product')
        ->setActionName('view')
        ->setParam('id', $productId);
// Return forward action
return $this->actionFactory->create(
    \Magento\Framework\App\Action\Forward::class
);Best Practices
Do's
- Use standard routing when possible
- Keep router match() method fast
- Return null if URL doesn't match
- Set appropriate sortOrder priority
- Document custom URL patterns
- Test with various URL formats
- Consider URL rewrite as alternative
- Handle edge cases gracefully
Don'ts
- Don't create custom routers unnecessarily
- Don't perform heavy operations in match()
- Don't forget to return null for non-matches
- Don't use too high/low sortOrder
- Don't duplicate existing router functionality
- Don't forget to register in di.xml
- Don't hardcode store/website logic
Common Use Cases
When to Use Custom Routers
| Use Case | Example URL | Solution | 
|---|---|---|
| Product SKU URLs | product/SKU-123 | Custom router matching SKU pattern | 
| Date-based archives | blog/2024/10/ | Custom router with date parsing | 
| Multi-level categories | shop/electronics/phones/ | Custom router with category tree lookup | 
| Short URLs | p/123,c/456 | Custom router with prefix matching | 
| Legacy URL compatibility | Old site URL patterns | Custom router for backwards compatibility | 
Debugging Routers
Troubleshooting Tips
Enable Developer Mode:
bin/magento deploy:mode:set developerCheck Router Registration:
bin/magento dev:di:info Magento\Framework\App\RouterListAdd Logging:
public function match(RequestInterface $request)
{
    $this->logger->debug('Custom Router: ' . $request->getPathInfo());
    
    // Your matching logic
    
    if ($matched) {
        $this->logger->debug('Custom Router: MATCHED');
        return $action;
    }
    
    $this->logger->debug('Custom Router: Not matched');
    return null;
}Clear Cache:
bin/magento cache:clean config
bin/magento cache:clean full_pageExam Tips
Key Points to Remember
- Two routing types: Standard routing and URL Rewrites
- Standard URL format: frontname/controller/action
- Route configuration: etc/frontend/routes.xml (or adminhtml)
- Front Controller: Loops through routers to find match
- Custom router interface: RouterInterfacewithmatch()method
- Match method: Returns ActionInterfaceornull
- Registration: Register router in di.xml with sortOrder
- Default routers: Base (20), UrlRewrite (40), CMS (60), Default (100)
- Action types: Forward, Redirect, or custom ActionInterface
- Controller path: Vendor/Module/Controller/Path/Action.php
- Request modification: Use setModuleName(), setControllerName(), setActionName()
- Priority matters: Lower sortOrder = earlier execution
